--- /dev/null
- [d7b5cbf0...master](https://github.com/rust-lang/rust-clippy/compare/d7b5cbf0...master)
+# 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.
+
+## Unreleased / In Rust Nightly
+
- Current stable, released 2022-08-11
++[3c7e7dbc...master](https://github.com/rust-lang/rust-clippy/compare/3c7e7dbc...master)
++
++## Rust 1.64
++
++Current stable, 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_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_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_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_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_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_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
+[`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
+[`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
+[`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
+[`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
+[`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_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_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
- version = "0.1.65"
+[package]
+name = "clippy"
- rustc_tools_util = { path = "rustc_tools_util" }
++version = "0.1.66"
+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"
- compiletest_rs = { version = "0.8", features = ["tmp"] }
++rustc_tools_util = "0.2.1"
+tempfile = { version = "3.2", optional = true }
+termize = "0.1"
+
+[dev-dependencies]
- rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
++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
- 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 that 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.
-
- To deactivate the “for further information visit *lint-link*” message you can
- define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable.
-
+# Clippy
+
+[![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)
+[![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.
++
++> **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.
++
++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
- eprintln!("error: A command failed! `{}`\nstderr: {}", command, stderr);
+use crate::clippy_project_root;
+use itertools::Itertools;
+use shell_escape::escape;
+use std::ffi::{OsStr, OsString};
+use std::path::Path;
+use std::process::{self, Command, Stdio};
+use std::{fs, io};
+use walkdir::WalkDir;
+
+#[derive(Debug)]
+pub enum CliError {
+ CommandFailed(String, String),
+ IoError(io::Error),
+ RustfmtNotInstalled,
+ WalkDirError(walkdir::Error),
+ IntellijSetupActive,
+}
+
+impl From<io::Error> for CliError {
+ fn from(error: io::Error) -> Self {
+ Self::IoError(error)
+ }
+}
+
+impl From<walkdir::Error> for CliError {
+ fn from(error: walkdir::Error) -> Self {
+ Self::WalkDirError(error)
+ }
+}
+
+struct FmtContext {
+ check: bool,
+ verbose: bool,
+ rustfmt_path: String,
+}
+
+// the "main" function of cargo dev fmt
+pub fn run(check: bool, verbose: bool) {
+ fn try_run(context: &FmtContext) -> Result<bool, CliError> {
+ let mut success = true;
+
+ let project_root = clippy_project_root();
+
+ // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
+ // format because rustfmt would also format the entire rustc repo as it is a local
+ // dependency
+ if fs::read_to_string(project_root.join("Cargo.toml"))
+ .expect("Failed to read clippy Cargo.toml")
+ .contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
+ {
+ return Err(CliError::IntellijSetupActive);
+ }
+
+ rustfmt_test(context)?;
+
+ success &= cargo_fmt(context, project_root.as_path())?;
+ success &= cargo_fmt(context, &project_root.join("clippy_dev"))?;
+ success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
+ success &= cargo_fmt(context, &project_root.join("lintcheck"))?;
+
+ let chunks = WalkDir::new(project_root.join("tests"))
+ .into_iter()
+ .filter_map(|entry| {
+ let entry = entry.expect("failed to find tests");
+ let path = entry.path();
+
+ if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" {
+ None
+ } else {
+ Some(entry.into_path().into_os_string())
+ }
+ })
+ .chunks(250);
+
+ for chunk in &chunks {
+ success &= rustfmt(context, chunk)?;
+ }
+
+ Ok(success)
+ }
+
+ fn output_err(err: CliError) {
+ match err {
+ CliError::CommandFailed(command, stderr) => {
- eprintln!("error: {}", err);
++ eprintln!("error: A command failed! `{command}`\nstderr: {stderr}");
+ },
+ CliError::IoError(err) => {
- eprintln!("error: {}", err);
++ eprintln!("error: {err}");
+ },
+ CliError::RustfmtNotInstalled => {
+ eprintln!("error: rustfmt nightly is not installed.");
+ },
+ CliError::WalkDirError(err) => {
++ eprintln!("error: {err}");
+ },
+ CliError::IntellijSetupActive => {
+ eprintln!(
+ "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.
+Not formatting because that would format the local repo as well!
+Please revert the changes to Cargo.tomls with `cargo dev remove intellij`."
+ );
+ },
+ }
+ }
+
+ let output = Command::new("rustup")
+ .args(["which", "rustfmt"])
+ .stderr(Stdio::inherit())
+ .output()
+ .expect("error running `rustup which rustfmt`");
+ if !output.status.success() {
+ eprintln!("`rustup which rustfmt` did not execute successfully");
+ process::exit(1);
+ }
+ let mut rustfmt_path = String::from_utf8(output.stdout).expect("invalid rustfmt path");
+ rustfmt_path.truncate(rustfmt_path.trim_end().len());
+
+ let context = FmtContext {
+ check,
+ verbose,
+ rustfmt_path,
+ };
+ let result = try_run(&context);
+ let code = match result {
+ Ok(true) => 0,
+ Ok(false) => {
+ eprintln!();
+ eprintln!("Formatting check failed.");
+ eprintln!("Run `cargo dev fmt` to update formatting.");
+ 1
+ },
+ Err(err) => {
+ output_err(err);
+ 1
+ },
+ };
+ process::exit(code);
+}
+
+fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
+ let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).collect();
+
+ format!(
+ "cd {} && {} {}",
+ escape(dir.as_ref().to_string_lossy()),
+ escape(program.as_ref().to_string_lossy()),
+ arg_display.join(" ")
+ )
+}
+
+fn exec(
+ context: &FmtContext,
+ program: impl AsRef<OsStr>,
+ dir: impl AsRef<Path>,
+ args: &[impl AsRef<OsStr>],
+) -> Result<bool, CliError> {
+ if context.verbose {
+ println!("{}", format_command(&program, &dir, args));
+ }
+
+ let output = Command::new(&program)
+ .env("RUSTFMT", &context.rustfmt_path)
+ .current_dir(&dir)
+ .args(args.iter())
+ .output()
+ .unwrap();
+ let success = output.status.success();
+
+ if !context.check && !success {
+ let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
+ return Err(CliError::CommandFailed(
+ format_command(&program, &dir, args),
+ String::from(stderr),
+ ));
+ }
+
+ Ok(success)
+}
+
+fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
+ let mut args = vec!["fmt", "--all"];
+ if context.check {
+ args.push("--check");
+ }
+ let success = exec(context, "cargo", path, &args)?;
+
+ Ok(success)
+}
+
+fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> {
+ let program = "rustfmt";
+ let dir = std::env::current_dir()?;
+ let args = &["--version"];
+
+ if context.verbose {
+ println!("{}", format_command(program, &dir, args));
+ }
+
+ let output = Command::new(program).current_dir(&dir).args(args.iter()).output()?;
+
+ if output.status.success() {
+ Ok(())
+ } else if std::str::from_utf8(&output.stderr)
+ .unwrap_or("")
+ .starts_with("error: 'rustfmt' is not installed")
+ {
+ Err(CliError::RustfmtNotInstalled)
+ } else {
+ Err(CliError::CommandFailed(
+ format_command(program, &dir, args),
+ std::str::from_utf8(&output.stderr).unwrap_or("").to_string(),
+ ))
+ }
+}
+
+fn rustfmt(context: &FmtContext, paths: impl Iterator<Item = OsString>) -> Result<bool, CliError> {
+ let mut args = Vec::new();
+ if context.check {
+ args.push(OsString::from("--check"));
+ }
+ args.extend(paths);
+
+ let success = exec(context, &context.rustfmt_path, std::env::current_dir()?, &args)?;
+
+ Ok(success)
+}
--- /dev/null
- Err(e) => eprintln!("Unable to create lint: {}", e),
+#![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 clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue};
+use clippy_dev::{bless, dogfood, fmt, lint, new_lint, serve, setup, update_lints};
+use indoc::indoc;
+
+fn main() {
+ let matches = get_clap_config();
+
+ match matches.subcommand() {
+ Some(("bless", matches)) => {
+ bless::bless(matches.contains_id("ignore-timestamp"));
+ },
+ Some(("dogfood", matches)) => {
+ dogfood::dogfood(
+ matches.contains_id("fix"),
+ matches.contains_id("allow-dirty"),
+ matches.contains_id("allow-staged"),
+ );
+ },
+ Some(("fmt", matches)) => {
+ fmt::run(matches.contains_id("check"), matches.contains_id("verbose"));
+ },
+ Some(("update_lints", matches)) => {
+ if matches.contains_id("print-only") {
+ update_lints::print_lints();
+ } else if matches.contains_id("check") {
+ update_lints::update(update_lints::UpdateMode::Check);
+ } else {
+ update_lints::update(update_lints::UpdateMode::Change);
+ }
+ },
+ Some(("new_lint", matches)) => {
+ match new_lint::create(
+ matches.get_one::<String>("pass"),
+ matches.get_one::<String>("name"),
+ matches.get_one::<String>("category").map(String::as_str),
+ matches.get_one::<String>("type").map(String::as_str),
+ matches.contains_id("msrv"),
+ ) {
+ Ok(_) => update_lints::update(update_lints::UpdateMode::Change),
++ Err(e) => eprintln!("Unable to create lint: {e}"),
+ }
+ },
+ Some(("setup", sub_command)) => match sub_command.subcommand() {
+ Some(("intellij", matches)) => {
+ if matches.contains_id("remove") {
+ setup::intellij::remove_rustc_src();
+ } else {
+ setup::intellij::setup_rustc_src(
+ matches
+ .get_one::<String>("rustc-repo-path")
+ .expect("this field is mandatory and therefore always valid"),
+ );
+ }
+ },
+ Some(("git-hook", matches)) => {
+ if matches.contains_id("remove") {
+ setup::git_hook::remove_hook();
+ } else {
+ setup::git_hook::install_hook(matches.contains_id("force-override"));
+ }
+ },
+ Some(("vscode-tasks", matches)) => {
+ if matches.contains_id("remove") {
+ setup::vscode::remove_tasks();
+ } else {
+ setup::vscode::install_tasks(matches.contains_id("force-override"));
+ }
+ },
+ _ => {},
+ },
+ Some(("remove", sub_command)) => match sub_command.subcommand() {
+ Some(("git-hook", _)) => setup::git_hook::remove_hook(),
+ Some(("intellij", _)) => setup::intellij::remove_rustc_src(),
+ Some(("vscode-tasks", _)) => setup::vscode::remove_tasks(),
+ _ => {},
+ },
+ Some(("serve", matches)) => {
+ let port = *matches.get_one::<u16>("port").unwrap();
+ let lint = matches.get_one::<String>("lint");
+ serve::run(port, lint);
+ },
+ Some(("lint", matches)) => {
+ let path = matches.get_one::<String>("path").unwrap();
+ let args = matches.get_many::<String>("args").into_iter().flatten();
+ lint::run(path, args);
+ },
+ Some(("rename_lint", matches)) => {
+ let old_name = matches.get_one::<String>("old_name").unwrap();
+ let new_name = matches.get_one::<String>("new_name").unwrap_or(old_name);
+ let uplift = matches.contains_id("uplift");
+ update_lints::rename(old_name, new_name, uplift);
+ },
+ Some(("deprecate", matches)) => {
+ let name = matches.get_one::<String>("name").unwrap();
+ let reason = matches.get_one("reason");
+ update_lints::deprecate(name, reason);
+ },
+ _ => {},
+ }
+}
+
+fn get_clap_config() -> ArgMatches {
+ Command::new("Clippy developer tooling")
+ .arg_required_else_help(true)
+ .subcommands([
+ Command::new("bless").about("bless the test output changes").arg(
+ Arg::new("ignore-timestamp")
+ .long("ignore-timestamp")
+ .help("Include files updated before clippy was built"),
+ ),
+ Command::new("dogfood").about("Runs the dogfood test").args([
+ Arg::new("fix").long("fix").help("Apply the suggestions when possible"),
+ Arg::new("allow-dirty")
+ .long("allow-dirty")
+ .help("Fix code even if the working directory has changes")
+ .requires("fix"),
+ Arg::new("allow-staged")
+ .long("allow-staged")
+ .help("Fix code even if the working directory has staged changes")
+ .requires("fix"),
+ ]),
+ Command::new("fmt")
+ .about("Run rustfmt on all projects and tests")
+ .args([
+ Arg::new("check").long("check").help("Use the rustfmt --check option"),
+ Arg::new("verbose").short('v').long("verbose").help("Echo commands run"),
+ ]),
+ Command::new("update_lints")
+ .about("Updates lint registration and information from the source code")
+ .long_about(
+ "Makes sure that:\n \
+ * the lint count in README.md is correct\n \
+ * the changelog contains markdown link references at the bottom\n \
+ * all lint groups include the correct lints\n \
+ * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \
+ * all lints are registered in the lint store",
+ )
+ .args([
+ Arg::new("print-only").long("print-only").help(
+ "Print a table of lints to STDOUT. \
+ This does not include deprecated and internal lints. \
+ (Does not modify any files)",
+ ),
+ Arg::new("check")
+ .long("check")
+ .help("Checks that `cargo dev update_lints` has been run. Used on CI."),
+ ]),
+ Command::new("new_lint")
+ .about("Create new lint and run `cargo dev update_lints`")
+ .args([
+ Arg::new("pass")
+ .short('p')
+ .long("pass")
+ .help("Specify whether the lint runs during the early or late pass")
+ .takes_value(true)
+ .value_parser([PossibleValue::new("early"), PossibleValue::new("late")])
+ .conflicts_with("type")
+ .required_unless_present("type"),
+ Arg::new("name")
+ .short('n')
+ .long("name")
+ .help("Name of the new lint in snake case, ex: fn_too_long")
+ .takes_value(true)
+ .required(true),
+ Arg::new("category")
+ .short('c')
+ .long("category")
+ .help("What category the lint belongs to")
+ .default_value("nursery")
+ .value_parser([
+ PossibleValue::new("style"),
+ PossibleValue::new("correctness"),
+ PossibleValue::new("suspicious"),
+ PossibleValue::new("complexity"),
+ PossibleValue::new("perf"),
+ PossibleValue::new("pedantic"),
+ PossibleValue::new("restriction"),
+ PossibleValue::new("cargo"),
+ PossibleValue::new("nursery"),
+ PossibleValue::new("internal"),
+ PossibleValue::new("internal_warn"),
+ ])
+ .takes_value(true),
+ Arg::new("type")
+ .long("type")
+ .help("What directory the lint belongs in")
+ .takes_value(true)
+ .required(false),
+ Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint"),
+ ]),
+ Command::new("setup")
+ .about("Support for setting up your personal development environment")
+ .arg_required_else_help(true)
+ .subcommands([
+ Command::new("intellij")
+ .about("Alter dependencies so Intellij Rust can find rustc internals")
+ .args([
+ Arg::new("remove")
+ .long("remove")
+ .help("Remove the dependencies added with 'cargo dev setup intellij'")
+ .required(false),
+ Arg::new("rustc-repo-path")
+ .long("repo-path")
+ .short('r')
+ .help("The path to a rustc repo that will be used for setting the dependencies")
+ .takes_value(true)
+ .value_name("path")
+ .conflicts_with("remove")
+ .required(true),
+ ]),
+ Command::new("git-hook")
+ .about("Add a pre-commit git hook that formats your code to make it look pretty")
+ .args([
+ Arg::new("remove")
+ .long("remove")
+ .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'")
+ .required(false),
+ Arg::new("force-override")
+ .long("force-override")
+ .short('f')
+ .help("Forces the override of an existing git pre-commit hook")
+ .required(false),
+ ]),
+ Command::new("vscode-tasks")
+ .about("Add several tasks to vscode for formatting, validation and testing")
+ .args([
+ Arg::new("remove")
+ .long("remove")
+ .help("Remove the tasks added with 'cargo dev setup vscode-tasks'")
+ .required(false),
+ Arg::new("force-override")
+ .long("force-override")
+ .short('f')
+ .help("Forces the override of existing vscode tasks")
+ .required(false),
+ ]),
+ ]),
+ Command::new("remove")
+ .about("Support for undoing changes done by the setup command")
+ .arg_required_else_help(true)
+ .subcommands([
+ Command::new("git-hook").about("Remove any existing pre-commit git hook"),
+ Command::new("vscode-tasks").about("Remove any existing vscode tasks"),
+ Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"),
+ ]),
+ Command::new("serve")
+ .about("Launch a local 'ALL the Clippy Lints' website in a browser")
+ .args([
+ Arg::new("port")
+ .long("port")
+ .short('p')
+ .help("Local port for the http server")
+ .default_value("8000")
+ .value_parser(clap::value_parser!(u16)),
+ Arg::new("lint").help("Which lint's page to load initially (optional)"),
+ ]),
+ Command::new("lint")
+ .about("Manually run clippy on a file or package")
+ .after_help(indoc! {"
+ EXAMPLES
+ Lint a single file:
+ cargo dev lint tests/ui/attrs.rs
+
+ Lint a package directory:
+ cargo dev lint tests/ui-cargo/wildcard_dependencies/fail
+ cargo dev lint ~/my-project
+
+ Run rustfix:
+ cargo dev lint ~/my-project -- --fix
+
+ Set lint levels:
+ cargo dev lint file.rs -- -W clippy::pedantic
+ cargo dev lint ~/my-project -- -- -W clippy::pedantic
+ "})
+ .args([
+ Arg::new("path")
+ .required(true)
+ .help("The path to a file or package directory to lint"),
+ Arg::new("args")
+ .action(ArgAction::Append)
+ .help("Pass extra arguments to cargo/clippy-driver"),
+ ]),
+ Command::new("rename_lint").about("Renames the given lint").args([
+ Arg::new("old_name")
+ .index(1)
+ .required(true)
+ .help("The name of the lint to rename"),
+ Arg::new("new_name")
+ .index(2)
+ .required_unless_present("uplift")
+ .help("The new name of the lint"),
+ Arg::new("uplift")
+ .long("uplift")
+ .help("This lint will be uplifted into rustc"),
+ ]),
+ Command::new("deprecate").about("Deprecates the given lint").args([
+ Arg::new("name")
+ .index(1)
+ .required(true)
+ .help("The name of the lint to deprecate"),
+ Arg::new("reason")
+ .long("reason")
+ .short('r')
+ .required(false)
+ .takes_value(true)
+ .help("The reason for deprecation"),
+ ]),
+ ])
+ .get_matches()
+}
--- /dev/null
- use indoc::{indoc, writedoc};
+use crate::clippy_project_root;
- let message = format!("{}: {}", text.as_ref(), e);
++use indoc::{formatdoc, writedoc};
+use std::fmt::Write as _;
+use std::fs::{self, OpenOptions};
+use std::io::prelude::*;
+use std::io::{self, ErrorKind};
+use std::path::{Path, PathBuf};
+
+struct LintData<'a> {
+ pass: &'a str,
+ name: &'a str,
+ category: &'a str,
+ ty: Option<&'a str>,
+ project_root: PathBuf,
+}
+
+trait Context {
+ fn context<C: AsRef<str>>(self, text: C) -> Self;
+}
+
+impl<T> Context for io::Result<T> {
+ fn context<C: AsRef<str>>(self, text: C) -> Self {
+ match self {
+ Ok(t) => Ok(t),
+ Err(e) => {
- println!("Generated lint file: `{}`", lint_path);
++ let message = format!("{}: {e}", text.as_ref());
+ Err(io::Error::new(ErrorKind::Other, message))
+ },
+ }
+ }
+}
+
+/// Creates the files required to implement and test a new lint and runs `update_lints`.
+///
+/// # Errors
+///
+/// This function errors out if the files couldn't be created or written to.
+pub fn create(
+ pass: Option<&String>,
+ lint_name: Option<&String>,
+ category: Option<&str>,
+ mut ty: Option<&str>,
+ msrv: bool,
+) -> io::Result<()> {
+ if category == Some("cargo") && ty.is_none() {
+ // `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo`
+ ty = Some("cargo");
+ }
+
+ let lint = LintData {
+ pass: pass.map_or("", String::as_str),
+ name: lint_name.expect("`name` argument is validated by clap"),
+ category: category.expect("`category` argument is validated by clap"),
+ ty,
+ project_root: clippy_project_root(),
+ };
+
+ create_lint(&lint, msrv).context("Unable to create lint implementation")?;
+ create_test(&lint).context("Unable to create a test for the new lint")?;
+
+ if lint.ty.is_none() {
+ add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")?;
+ }
+
+ Ok(())
+}
+
+fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
+ if let Some(ty) = lint.ty {
+ create_lint_for_ty(lint, enable_msrv, ty)
+ } else {
+ let lint_contents = get_lint_file_contents(lint, enable_msrv);
+ let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
+ write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())?;
- let header = format!("// compile-flags: --crate-name={}", lint_name);
++ println!("Generated lint file: `{lint_path}`");
+
+ Ok(())
+ }
+}
+
+fn create_test(lint: &LintData<'_>) -> io::Result<()> {
+ fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
+ let mut path = location.into().join(case);
+ fs::create_dir(&path)?;
+ write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
+
+ path.push("src");
+ fs::create_dir(&path)?;
- println!("Generated test file: `{}`", test_path);
++ let header = format!("// compile-flags: --crate-name={lint_name}");
+ write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
+
+ Ok(())
+ }
+
+ if lint.category == "cargo" {
+ let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
+ let test_dir = lint.project_root.join(&relative_test_dir);
+ fs::create_dir(&test_dir)?;
+
+ create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
+ create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")?;
+
+ println!("Generated test directories: `{relative_test_dir}/pass`, `{relative_test_dir}/fail`");
+ } else {
+ let test_path = format!("tests/ui/{}.rs", lint.name);
+ let test_contents = get_test_file_contents(lint.name, None);
+ write_file(lint.project_root.join(&test_path), test_contents)?;
+
- let mut contents = format!(
- indoc! {"
- #![allow(unused)]
- #![warn(clippy::{})]
-
- fn main() {{
- // test code goes here
- }}
- "},
- lint_name
++ println!("Generated test file: `{test_path}`");
+ }
+
+ Ok(())
+}
+
+fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
+ let path = "clippy_lints/src/lib.rs";
+ let mut lib_rs = fs::read_to_string(path).context("reading")?;
+
+ let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment");
+
+ let new_lint = if enable_msrv {
+ format!(
+ "store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(msrv)));\n ",
+ lint_pass = lint.pass,
+ ctor_arg = if lint.pass == "late" { "_" } else { "" },
+ module_name = lint.name,
+ camel_name = to_camel_case(lint.name),
+ )
+ } else {
+ format!(
+ "store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n ",
+ lint_pass = lint.pass,
+ ctor_arg = if lint.pass == "late" { "_" } else { "" },
+ module_name = lint.name,
+ camel_name = to_camel_case(lint.name),
+ )
+ };
+
+ lib_rs.insert_str(comment_start, &new_lint);
+
+ fs::write(path, lib_rs).context("writing")
+}
+
+fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
+ fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
+ OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(path)?
+ .write_all(contents)
+ }
+
+ inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
+}
+
+fn to_camel_case(name: &str) -> String {
+ name.split('_')
+ .map(|s| {
+ if s.is_empty() {
+ String::new()
+ } else {
+ [&s[0..1].to_uppercase(), &s[1..]].concat()
+ }
+ })
+ .collect()
+}
+
+pub(crate) fn get_stabilization_version() -> String {
+ fn parse_manifest(contents: &str) -> Option<String> {
+ let version = contents
+ .lines()
+ .filter_map(|l| l.split_once('='))
+ .find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?;
+ let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else {
+ return None;
+ };
+ let (minor, patch) = version.split_once('.')?;
+ Some(format!(
+ "{}.{}.0",
+ minor.parse::<u32>().ok()?,
+ patch.parse::<u32>().ok()?
+ ))
+ }
+ let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`");
+ parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
+}
+
+fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
- contents = format!("{}\n{}", header, contents);
++ let mut contents = formatdoc!(
++ r#"
++ #![allow(unused)]
++ #![warn(clippy::{lint_name})]
++
++ fn main() {{
++ // test code goes here
++ }}
++ "#
+ );
+
+ if let Some(header) = header_commands {
- format!(
- indoc! {r#"
- # {}
-
- [package]
- name = "{}"
- version = "0.1.0"
- publish = false
-
- [workspace]
- "#},
- hint, lint_name
++ contents = format!("{header}\n{contents}");
+ }
+
+ contents
+}
+
+fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
- format!(
- indoc! {"
- use clippy_utils::msrvs;
- {pass_import}
- use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
- use rustc_semver::RustcVersion;
- use rustc_session::{{declare_tool_lint, impl_lint_pass}};
++ formatdoc!(
++ r#"
++ # {hint}
++
++ [package]
++ name = "{lint_name}"
++ version = "0.1.0"
++ publish = false
++
++ [workspace]
++ "#
+ )
+}
+
+fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
+ let mut result = String::new();
+
+ let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
+ "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
+ "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
+ _ => {
+ unreachable!("`pass_type` should only ever be `early` or `late`!");
+ },
+ };
+
+ let lint_name = lint.name;
+ let category = lint.category;
+ let name_camel = to_camel_case(lint.name);
+ let name_upper = lint_name.to_uppercase();
+
+ result.push_str(&if enable_msrv {
- "},
- pass_type = pass_type,
- pass_import = pass_import,
- context_import = context_import,
++ formatdoc!(
++ r#"
++ use clippy_utils::msrvs;
++ {pass_import}
++ use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
++ use rustc_semver::RustcVersion;
++ use rustc_session::{{declare_tool_lint, impl_lint_pass}};
+
- format!(
- indoc! {"
- {pass_import}
- use rustc_lint::{{{context_import}, {pass_type}}};
- use rustc_session::{{declare_lint_pass, declare_tool_lint}};
-
- "},
- pass_import = pass_import,
- pass_type = pass_type,
- context_import = context_import
++ "#
+ )
+ } else {
- format!(
- indoc! {"
- pub struct {name_camel} {{
- msrv: Option<RustcVersion>,
- }}
++ formatdoc!(
++ r#"
++ {pass_import}
++ use rustc_lint::{{{context_import}, {pass_type}}};
++ use rustc_session::{{declare_lint_pass, declare_tool_lint}};
++
++ "#
+ )
+ });
+
+ let _ = write!(result, "{}", get_lint_declaration(&name_upper, category));
+
+ result.push_str(&if enable_msrv {
- impl {name_camel} {{
- #[must_use]
- pub fn new(msrv: Option<RustcVersion>) -> Self {{
- Self {{ msrv }}
- }}
++ formatdoc!(
++ r#"
++ pub struct {name_camel} {{
++ msrv: Option<RustcVersion>,
++ }}
+
- impl_lint_pass!({name_camel} => [{name_upper}]);
++ impl {name_camel} {{
++ #[must_use]
++ pub fn new(msrv: Option<RustcVersion>) -> Self {{
++ Self {{ msrv }}
+ }}
++ }}
+
- impl {pass_type}{pass_lifetimes} for {name_camel} {{
- extract_msrv_attr!({context_import});
- }}
++ impl_lint_pass!({name_camel} => [{name_upper}]);
+
- // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
- // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
- // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
- "},
- pass_type = pass_type,
- pass_lifetimes = pass_lifetimes,
- name_upper = name_upper,
- name_camel = name_camel,
- context_import = context_import,
++ impl {pass_type}{pass_lifetimes} for {name_camel} {{
++ extract_msrv_attr!({context_import});
++ }}
+
- format!(
- indoc! {"
- declare_lint_pass!({name_camel} => [{name_upper}]);
++ // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
++ // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
++ // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
++ "#
+ )
+ } else {
- impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
- "},
- pass_type = pass_type,
- pass_lifetimes = pass_lifetimes,
- name_upper = name_upper,
- name_camel = name_camel,
++ formatdoc!(
++ r#"
++ declare_lint_pass!({name_camel} => [{name_upper}]);
+
- format!(
- indoc! {r#"
++ impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
++ "#
+ )
+ });
+
+ result
+}
+
+fn get_lint_declaration(name_upper: &str, category: &str) -> String {
- #[clippy::version = "{version}"]
++ formatdoc!(
++ r#"
+ declare_clippy_lint! {{
+ /// ### What it does
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// ```
- "#},
- version = get_stabilization_version(),
- name_upper = name_upper,
- category = category,
++ #[clippy::version = "{}"]
+ pub {name_upper},
+ {category},
+ "default lint description"
+ }}
- let ty_dir = lint.project_root.join(format!("clippy_lints/src/{}", ty));
++ "#,
++ get_stabilization_version(),
+ )
+}
+
+fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::Result<()> {
+ match ty {
+ "cargo" => assert_eq!(
+ lint.category, "cargo",
+ "Lints of type `cargo` must have the `cargo` category"
+ ),
+ _ if lint.category == "cargo" => panic!("Lints of category `cargo` must have the `cargo` type"),
+ _ => {},
+ }
+
- println!("Generated lint file: `clippy_lints/src/{}/{}.rs`", ty, lint.name);
++ let ty_dir = lint.project_root.join(format!("clippy_lints/src/{ty}"));
+ assert!(
+ ty_dir.exists() && ty_dir.is_dir(),
+ "Directory `{}` does not exist!",
+ ty_dir.display()
+ );
+
+ let lint_file_path = ty_dir.join(format!("{}.rs", lint.name));
+ assert!(
+ !lint_file_path.exists(),
+ "File `{}` already exists",
+ lint_file_path.display()
+ );
+
+ let mod_file_path = ty_dir.join("mod.rs");
+ let context_import = setup_mod_file(&mod_file_path, lint)?;
+
+ let name_upper = lint.name.to_uppercase();
+ let mut lint_file_contents = String::new();
+
+ if enable_msrv {
+ let _ = writedoc!(
+ lint_file_contents,
+ r#"
+ use clippy_utils::{{meets_msrv, msrvs}};
+ use rustc_lint::{{{context_import}, LintContext}};
+ use rustc_semver::RustcVersion;
+
+ use super::{name_upper};
+
+ // TODO: Adjust the parameters as necessary
+ pub(super) fn check(cx: &{context_import}, msrv: Option<RustcVersion>) {{
+ if !meets_msrv(msrv, todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
+ return;
+ }}
+ todo!();
+ }}
+ "#,
+ context_import = context_import,
+ name_upper = name_upper,
+ );
+ } else {
+ let _ = writedoc!(
+ lint_file_contents,
+ r#"
+ use rustc_lint::{{{context_import}, LintContext}};
+
+ use super::{name_upper};
+
+ // TODO: Adjust the parameters as necessary
+ pub(super) fn check(cx: &{context_import}) {{
+ todo!();
+ }}
+ "#,
+ context_import = context_import,
+ name_upper = name_upper,
+ );
+ }
+
+ write_file(lint_file_path.as_path(), lint_file_contents)?;
- "Be sure to add a call to `{}::check` in `clippy_lints/src/{}/mod.rs`!",
- lint.name, ty
++ println!("Generated lint file: `clippy_lints/src/{ty}/{}.rs`", lint.name);
+ println!(
- let _ = write!(new_arr_content, "\n {},", ident);
++ "Be sure to add a call to `{}::check` in `clippy_lints/src/{ty}/mod.rs`!",
++ lint.name
+ );
+
+ Ok(())
+}
+
+#[allow(clippy::too_many_lines)]
+fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> {
+ use super::update_lints::{match_tokens, LintDeclSearchResult};
+ use rustc_lexer::TokenKind;
+
+ let lint_name_upper = lint.name.to_uppercase();
+
+ let mut file_contents = fs::read_to_string(path)?;
+ assert!(
+ !file_contents.contains(&lint_name_upper),
+ "Lint `{}` already defined in `{}`",
+ lint.name,
+ path.display()
+ );
+
+ let mut offset = 0usize;
+ let mut last_decl_curly_offset = None;
+ let mut lint_context = None;
+
+ let mut iter = rustc_lexer::tokenize(&file_contents).map(|t| {
+ let range = offset..offset + t.len as usize;
+ offset = range.end;
+
+ LintDeclSearchResult {
+ token_kind: t.kind,
+ content: &file_contents[range.clone()],
+ range,
+ }
+ });
+
+ // Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
+ while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token_kind == TokenKind::Ident) {
+ let mut iter = iter
+ .by_ref()
+ .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
+
+ match content {
+ "declare_clippy_lint" => {
+ // matches `!{`
+ match_tokens!(iter, Bang OpenBrace);
+ if let Some(LintDeclSearchResult { range, .. }) =
+ iter.find(|result| result.token_kind == TokenKind::CloseBrace)
+ {
+ last_decl_curly_offset = Some(range.end);
+ }
+ },
+ "impl" => {
+ let mut token = iter.next();
+ match token {
+ // matches <'foo>
+ Some(LintDeclSearchResult {
+ token_kind: TokenKind::Lt,
+ ..
+ }) => {
+ match_tokens!(iter, Lifetime { .. } Gt);
+ token = iter.next();
+ },
+ None => break,
+ _ => {},
+ }
+
+ if let Some(LintDeclSearchResult {
+ token_kind: TokenKind::Ident,
+ content,
+ ..
+ }) = token
+ {
+ // Get the appropriate lint context struct
+ lint_context = match content {
+ "LateLintPass" => Some("LateContext"),
+ "EarlyLintPass" => Some("EarlyContext"),
+ _ => continue,
+ };
+ }
+ },
+ _ => {},
+ }
+ }
+
+ drop(iter);
+
+ let last_decl_curly_offset =
+ last_decl_curly_offset.unwrap_or_else(|| panic!("No lint declarations found in `{}`", path.display()));
+ let lint_context =
+ lint_context.unwrap_or_else(|| panic!("No lint pass implementation found in `{}`", path.display()));
+
+ // Add the lint declaration to `mod.rs`
+ file_contents.replace_range(
+ // Remove the trailing newline, which should always be present
+ last_decl_curly_offset..=last_decl_curly_offset,
+ &format!("\n\n{}", get_lint_declaration(&lint_name_upper, lint.category)),
+ );
+
+ // Add the lint to `impl_lint_pass`/`declare_lint_pass`
+ let impl_lint_pass_start = file_contents.find("impl_lint_pass!").unwrap_or_else(|| {
+ file_contents
+ .find("declare_lint_pass!")
+ .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`/`declare_lint_pass`"))
+ });
+
+ let mut arr_start = file_contents[impl_lint_pass_start..].find('[').unwrap_or_else(|| {
+ panic!("malformed `impl_lint_pass`/`declare_lint_pass`");
+ });
+
+ arr_start += impl_lint_pass_start;
+
+ let mut arr_end = file_contents[arr_start..]
+ .find(']')
+ .expect("failed to find `impl_lint_pass` terminator");
+
+ arr_end += arr_start;
+
+ let mut arr_content = file_contents[arr_start + 1..arr_end].to_string();
+ arr_content.retain(|c| !c.is_whitespace());
+
+ let mut new_arr_content = String::new();
+ for ident in arr_content
+ .split(',')
+ .chain(std::iter::once(&*lint_name_upper))
+ .filter(|s| !s.is_empty())
+ {
++ let _ = write!(new_arr_content, "\n {ident},");
+ }
+ new_arr_content.push('\n');
+
+ file_contents.replace_range(arr_start + 1..arr_end, &new_arr_content);
+
+ // Just add the mod declaration at the top, it'll be fixed by rustfmt
+ file_contents.insert_str(0, &format!("mod {};\n", &lint.name));
+
+ let mut file = OpenOptions::new()
+ .write(true)
+ .truncate(true)
+ .open(path)
+ .context(format!("trying to open: `{}`", path.display()))?;
+ file.write_all(file_contents.as_bytes())
+ .context(format!("writing to file: `{}`", path.display()))?;
+
+ Ok(lint_context)
+}
+
+#[test]
+fn test_camel_case() {
+ let s = "a_lint";
+ let s2 = to_camel_case(s);
+ assert_eq!(s2, "ALint");
+
+ let name = "a_really_long_new_lint";
+ let name2 = to_camel_case(name);
+ assert_eq!(name2, "AReallyLongNewLint");
+
+ let name3 = "lint__name";
+ let name4 = to_camel_case(name3);
+ assert_eq!(name4, "LintName");
+}
--- /dev/null
- None => format!("http://localhost:{}", port),
- Some(lint) => format!("http://localhost:{}/#{}", port, lint),
+use std::ffi::OsStr;
+use std::num::ParseIntError;
+use std::path::Path;
+use std::process::Command;
+use std::thread;
+use std::time::{Duration, SystemTime};
+
+/// # Panics
+///
+/// Panics if the python commands could not be spawned
+pub fn run(port: u16, lint: Option<&String>) -> ! {
+ let mut url = Some(match lint {
++ None => format!("http://localhost:{port}"),
++ Some(lint) => format!("http://localhost:{port}/#{lint}"),
+ });
+
+ loop {
+ if mtime("util/gh-pages/lints.json") < mtime("clippy_lints/src") {
+ Command::new("cargo")
+ .arg("collect-metadata")
+ .spawn()
+ .unwrap()
+ .wait()
+ .unwrap();
+ }
+ if let Some(url) = url.take() {
+ thread::spawn(move || {
+ Command::new("python3")
+ .arg("-m")
+ .arg("http.server")
+ .arg(port.to_string())
+ .current_dir("util/gh-pages")
+ .spawn()
+ .unwrap();
+ // Give some time for python to start
+ thread::sleep(Duration::from_millis(500));
+ // Launch browser after first export.py has completed and http.server is up
+ let _result = opener::open(url);
+ });
+ }
+ thread::sleep(Duration::from_millis(1000));
+ }
+}
+
+fn mtime(path: impl AsRef<Path>) -> SystemTime {
+ let path = path.as_ref();
+ if path.is_dir() {
+ path.read_dir()
+ .into_iter()
+ .flatten()
+ .flatten()
+ .map(|entry| mtime(&entry.path()))
+ .max()
+ .unwrap_or(SystemTime::UNIX_EPOCH)
+ } else {
+ path.metadata()
+ .and_then(|metadata| metadata.modified())
+ .unwrap_or(SystemTime::UNIX_EPOCH)
+ }
+}
+
+#[allow(clippy::missing_errors_doc)]
+pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> {
+ arg.to_string_lossy().parse::<u16>().map(|_| ())
+}
--- /dev/null
- Err(err) => eprintln!(
- "error: unable to copy `{}` to `{}` ({})",
- HOOK_SOURCE_FILE, HOOK_TARGET_FILE, err
- ),
+use std::fs;
+use std::path::Path;
+
+use super::verify_inside_clippy_dir;
+
+/// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
+/// I've decided against this for the sake of simplicity and to make sure that it doesn't install
+/// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
+/// for formatting and should therefor only be used in a normal clone of clippy
+const REPO_GIT_DIR: &str = ".git";
+const HOOK_SOURCE_FILE: &str = "util/etc/pre-commit.sh";
+const HOOK_TARGET_FILE: &str = ".git/hooks/pre-commit";
+
+pub fn install_hook(force_override: bool) {
+ if !check_precondition(force_override) {
+ return;
+ }
+
+ // So a little bit of a funny story. Git on unix requires the pre-commit file
+ // to have the `execute` permission to be set. The Rust functions for modifying
+ // these flags doesn't seem to work when executed with normal user permissions.
+ //
+ // However, there is a little hack that is also being used by Rust itself in their
+ // setup script. Git saves the `execute` flag when syncing files. This means
+ // that we can check in a file with execution permissions and the sync it to create
+ // a file with the flag set. We then copy this file here. The copy function will also
+ // include the `execute` permission.
+ match fs::copy(HOOK_SOURCE_FILE, HOOK_TARGET_FILE) {
+ Ok(_) => {
+ println!("info: the hook can be removed with `cargo dev remove git-hook`");
+ println!("git hook successfully installed");
+ },
- eprintln!("error: unable to delete existing pre-commit git hook ({})", err);
++ Err(err) => eprintln!("error: unable to copy `{HOOK_SOURCE_FILE}` to `{HOOK_TARGET_FILE}` ({err})"),
+ }
+}
+
+fn check_precondition(force_override: bool) -> bool {
+ if !verify_inside_clippy_dir() {
+ return false;
+ }
+
+ // Make sure that we can find the git repository
+ let git_path = Path::new(REPO_GIT_DIR);
+ if !git_path.exists() || !git_path.is_dir() {
+ eprintln!("error: clippy_dev was unable to find the `.git` directory");
+ return false;
+ }
+
+ // Make sure that we don't override an existing hook by accident
+ let path = Path::new(HOOK_TARGET_FILE);
+ if path.exists() {
+ if force_override {
+ return delete_git_hook_file(path);
+ }
+
+ eprintln!("error: there is already a pre-commit hook installed");
+ println!("info: use the `--force-override` flag to override the existing hook");
+ return false;
+ }
+
+ true
+}
+
+pub fn remove_hook() {
+ let path = Path::new(HOOK_TARGET_FILE);
+ if path.exists() {
+ if delete_git_hook_file(path) {
+ println!("git hook successfully removed");
+ }
+ } else {
+ println!("no pre-commit hook was found");
+ }
+}
+
+fn delete_git_hook_file(path: &Path) -> bool {
+ if let Err(err) = fs::remove_file(path) {
++ eprintln!("error: unable to delete existing pre-commit git hook ({err})");
+ false
+ } else {
+ true
+ }
+}
--- /dev/null
- eprintln!("error: unable to get the absolute path of rustc ({})", err);
+use std::fs;
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::{Path, PathBuf};
+
+// This module takes an absolute path to a rustc repo and alters the dependencies to point towards
+// the respective rustc subcrates instead of using extern crate xyz.
+// This allows IntelliJ to analyze rustc internals and show proper information inside Clippy
+// code. See https://github.com/rust-lang/rust-clippy/issues/5514 for details
+
+const RUSTC_PATH_SECTION: &str = "[target.'cfg(NOT_A_PLATFORM)'.dependencies]";
+const DEPENDENCIES_SECTION: &str = "[dependencies]";
+
+const CLIPPY_PROJECTS: &[ClippyProjectInfo] = &[
+ ClippyProjectInfo::new("root", "Cargo.toml", "src/driver.rs"),
+ ClippyProjectInfo::new("clippy_lints", "clippy_lints/Cargo.toml", "clippy_lints/src/lib.rs"),
+ ClippyProjectInfo::new("clippy_utils", "clippy_utils/Cargo.toml", "clippy_utils/src/lib.rs"),
+];
+
+/// Used to store clippy project information to later inject the dependency into.
+struct ClippyProjectInfo {
+ /// Only used to display information to the user
+ name: &'static str,
+ cargo_file: &'static str,
+ lib_rs_file: &'static str,
+}
+
+impl ClippyProjectInfo {
+ const fn new(name: &'static str, cargo_file: &'static str, lib_rs_file: &'static str) -> Self {
+ Self {
+ name,
+ cargo_file,
+ lib_rs_file,
+ }
+ }
+}
+
+pub fn setup_rustc_src(rustc_path: &str) {
+ let rustc_source_dir = match check_and_get_rustc_dir(rustc_path) {
+ Ok(path) => path,
+ Err(_) => return,
+ };
+
+ for project in CLIPPY_PROJECTS {
+ if inject_deps_into_project(&rustc_source_dir, project).is_err() {
+ return;
+ }
+ }
+
+ println!("info: the source paths can be removed again with `cargo dev remove intellij`");
+}
+
+fn check_and_get_rustc_dir(rustc_path: &str) -> Result<PathBuf, ()> {
+ let mut path = PathBuf::from(rustc_path);
+
+ if path.is_relative() {
+ match path.canonicalize() {
+ Ok(absolute_path) => {
+ println!("info: the rustc path was resolved to: `{}`", absolute_path.display());
+ path = absolute_path;
+ },
+ Err(err) => {
- eprintln!("error: unable to find the file `{}`", file_path);
++ eprintln!("error: unable to get the absolute path of rustc ({err})");
+ return Err(());
+ },
+ };
+ }
+
+ let path = path.join("compiler");
+ println!("info: looking for compiler sources at: {}", path.display());
+
+ if !path.exists() {
+ eprintln!("error: the given path does not exist");
+ return Err(());
+ }
+
+ if !path.is_dir() {
+ eprintln!("error: the given path is not a directory");
+ return Err(());
+ }
+
+ Ok(path)
+}
+
+fn inject_deps_into_project(rustc_source_dir: &Path, project: &ClippyProjectInfo) -> Result<(), ()> {
+ let cargo_content = read_project_file(project.cargo_file)?;
+ let lib_content = read_project_file(project.lib_rs_file)?;
+
+ if inject_deps_into_manifest(rustc_source_dir, project.cargo_file, &cargo_content, &lib_content).is_err() {
+ eprintln!(
+ "error: unable to inject dependencies into {} with the Cargo file {}",
+ project.name, project.cargo_file
+ );
+ Err(())
+ } else {
+ Ok(())
+ }
+}
+
+/// `clippy_dev` expects to be executed in the root directory of Clippy. This function
+/// loads the given file or returns an error. Having it in this extra function ensures
+/// that the error message looks nice.
+fn read_project_file(file_path: &str) -> Result<String, ()> {
+ let path = Path::new(file_path);
+ if !path.exists() {
- eprintln!("error: the file `{}` could not be read ({})", file_path, err);
++ eprintln!("error: unable to find the file `{file_path}`");
+ return Err(());
+ }
+
+ match fs::read_to_string(path) {
+ Ok(content) => Ok(content),
+ Err(err) => {
- eprintln!(
- "warn: dependencies are already setup inside {}, skipping file",
- manifest_path
- );
++ eprintln!("error: the file `{file_path}` could not be read ({err})");
+ Err(())
+ },
+ }
+}
+
+fn inject_deps_into_manifest(
+ rustc_source_dir: &Path,
+ manifest_path: &str,
+ cargo_toml: &str,
+ lib_rs: &str,
+) -> std::io::Result<()> {
+ // do not inject deps if we have already done so
+ if cargo_toml.contains(RUSTC_PATH_SECTION) {
- format!(
- "{dep} = {{ path = \"{source_path}/{dep}\" }}\n",
- dep = dep,
- source_path = rustc_source_dir.display()
- )
++ eprintln!("warn: dependencies are already setup inside {manifest_path}, skipping file");
+ return Ok(());
+ }
+
+ let extern_crates = lib_rs
+ .lines()
+ // only take dependencies starting with `rustc_`
+ .filter(|line| line.starts_with("extern crate rustc_"))
+ // we have something like "extern crate foo;", we only care about the "foo"
+ // extern crate rustc_middle;
+ // ^^^^^^^^^^^^
+ .map(|s| &s[13..(s.len() - 1)]);
+
+ let new_deps = extern_crates.map(|dep| {
+ // format the dependencies that are going to be put inside the Cargo.toml
- // println!("{}", new_manifest);
++ format!("{dep} = {{ path = \"{}/{dep}\" }}\n", rustc_source_dir.display())
+ });
+
+ // format a new [dependencies]-block with the new deps we need to inject
+ let mut all_deps = String::from("[target.'cfg(NOT_A_PLATFORM)'.dependencies]\n");
+ new_deps.for_each(|dep_line| {
+ all_deps.push_str(&dep_line);
+ });
+ all_deps.push_str("\n[dependencies]\n");
+
+ // replace "[dependencies]" with
+ // [dependencies]
+ // dep1 = { path = ... }
+ // dep2 = { path = ... }
+ // etc
+ let new_manifest = cargo_toml.replacen("[dependencies]\n", &all_deps, 1);
+
- println!("info: successfully setup dependencies inside {}", manifest_path);
++ // println!("{new_manifest}");
+ let mut file = File::create(manifest_path)?;
+ file.write_all(new_manifest.as_bytes())?;
+
- "error: unable to open file `{}` to remove rustc dependencies for {} ({})",
- project.cargo_file, project.name, err
++ println!("info: successfully setup dependencies inside {manifest_path}");
+
+ Ok(())
+}
+
+pub fn remove_rustc_src() {
+ for project in CLIPPY_PROJECTS {
+ remove_rustc_src_from_project(project);
+ }
+}
+
+fn remove_rustc_src_from_project(project: &ClippyProjectInfo) -> bool {
+ let mut cargo_content = if let Ok(content) = read_project_file(project.cargo_file) {
+ content
+ } else {
+ return false;
+ };
+ let section_start = if let Some(section_start) = cargo_content.find(RUSTC_PATH_SECTION) {
+ section_start
+ } else {
+ println!(
+ "info: dependencies could not be found in `{}` for {}, skipping file",
+ project.cargo_file, project.name
+ );
+ return true;
+ };
+
+ let end_point = if let Some(end_point) = cargo_content.find(DEPENDENCIES_SECTION) {
+ end_point
+ } else {
+ eprintln!(
+ "error: the end of the rustc dependencies section could not be found in `{}`",
+ project.cargo_file
+ );
+ return false;
+ };
+
+ cargo_content.replace_range(section_start..end_point, "");
+
+ match File::create(project.cargo_file) {
+ Ok(mut file) => {
+ file.write_all(cargo_content.as_bytes()).unwrap();
+ println!("info: successfully removed dependencies inside {}", project.cargo_file);
+ true
+ },
+ Err(err) => {
+ eprintln!(
++ "error: unable to open file `{}` to remove rustc dependencies for {} ({err})",
++ project.cargo_file, project.name
+ );
+ false
+ },
+ }
+}
--- /dev/null
- Err(err) => eprintln!(
- "error: unable to copy `{}` to `{}` ({})",
- TASK_SOURCE_FILE, TASK_TARGET_FILE, err
- ),
+use std::fs;
+use std::path::Path;
+
+use super::verify_inside_clippy_dir;
+
+const VSCODE_DIR: &str = ".vscode";
+const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json";
+const TASK_TARGET_FILE: &str = ".vscode/tasks.json";
+
+pub fn install_tasks(force_override: bool) {
+ if !check_install_precondition(force_override) {
+ return;
+ }
+
+ match fs::copy(TASK_SOURCE_FILE, TASK_TARGET_FILE) {
+ Ok(_) => {
+ println!("info: the task file can be removed with `cargo dev remove vscode-tasks`");
+ println!("vscode tasks successfully installed");
+ },
- eprintln!(
- "error: there is already a `task.json` file inside the `{}` directory",
- VSCODE_DIR
- );
++ Err(err) => eprintln!("error: unable to copy `{TASK_SOURCE_FILE}` to `{TASK_TARGET_FILE}` ({err})"),
+ }
+}
+
+fn check_install_precondition(force_override: bool) -> bool {
+ if !verify_inside_clippy_dir() {
+ return false;
+ }
+
+ let vs_dir_path = Path::new(VSCODE_DIR);
+ if vs_dir_path.exists() {
+ // verify the target will be valid
+ if !vs_dir_path.is_dir() {
+ eprintln!("error: the `.vscode` path exists but seems to be a file");
+ return false;
+ }
+
+ // make sure that we don't override any existing tasks by accident
+ let path = Path::new(TASK_TARGET_FILE);
+ if path.exists() {
+ if force_override {
+ return delete_vs_task_file(path);
+ }
+
- println!("info: created `{}` directory for clippy", VSCODE_DIR);
++ eprintln!("error: there is already a `task.json` file inside the `{VSCODE_DIR}` directory");
+ println!("info: use the `--force-override` flag to override the existing `task.json` file");
+ return false;
+ }
+ } else {
+ match fs::create_dir(vs_dir_path) {
+ Ok(_) => {
- eprintln!(
- "error: the task target directory `{}` could not be created ({})",
- VSCODE_DIR, err
- );
++ println!("info: created `{VSCODE_DIR}` directory for clippy");
+ },
+ Err(err) => {
- eprintln!("error: unable to delete the existing `tasks.json` file ({})", err);
++ eprintln!("error: the task target directory `{VSCODE_DIR}` could not be created ({err})");
+ },
+ }
+ }
+
+ true
+}
+
+pub fn remove_tasks() {
+ let path = Path::new(TASK_TARGET_FILE);
+ if path.exists() {
+ if delete_vs_task_file(path) {
+ try_delete_vs_directory_if_empty();
+ println!("vscode tasks successfully removed");
+ }
+ } else {
+ println!("no vscode tasks were found");
+ }
+}
+
+fn delete_vs_task_file(path: &Path) -> bool {
+ if let Err(err) = fs::remove_file(path) {
++ eprintln!("error: unable to delete the existing `tasks.json` file ({err})");
+ return false;
+ }
+
+ true
+}
+
+/// This function will try to delete the `.vscode` directory if it's empty.
+/// It may fail silently.
+fn try_delete_vs_directory_if_empty() {
+ let path = Path::new(VSCODE_DIR);
+ if path.read_dir().map_or(false, |mut iter| iter.next().is_none()) {
+ // The directory is empty. We just try to delete it but allow a silence
+ // fail as an empty `.vscode` directory is still valid
+ let _silence_result = fs::remove_dir(path);
+ } else {
+ // The directory is not empty or could not be read. Either way don't take
+ // any further actions
+ }
+}
--- /dev/null
- let usable_lints = Lint::usable_lints(lints);
- let mut sorted_usable_lints = usable_lints.clone();
- sorted_usable_lints.sort_by_key(|lint| lint.name.clone());
+use crate::clippy_project_root;
+use aho_corasick::AhoCorasickBuilder;
+use indoc::writedoc;
+use itertools::Itertools;
+use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
+use std::collections::{BTreeSet, 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);
+}
+
+fn generate_lint_files(
+ update_mode: UpdateMode,
+ lints: &[Lint],
+ deprecated_lints: &[DeprecatedLint],
+ renamed_lints: &[RenamedLint],
+) {
+ let internal_lints = Lint::internal_lints(lints);
- writeln!(res, "[`{}`]: {}#{}", lint, DOCS_LINK, lint).unwrap();
++ 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, "mod {};", lint_mod).unwrap();
++ 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() {
- &format!("clippy_lints/src/lib.register_{}.rs", lint_group),
++ writeln!(res, "mod {lint_mod};").unwrap();
+ }
+ },
+ );
+
+ process_file(
+ "clippy_lints/src/lib.register_lints.rs",
+ update_mode,
+ &gen_register_lint_list(internal_lints.iter(), usable_lints.iter()),
+ );
+ process_file(
+ "clippy_lints/src/lib.deprecated.rs",
+ update_mode,
+ &gen_deprecated(deprecated_lints),
+ );
+
+ 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(
- println!("{}", header);
++ &format!("clippy_lints/src/lib.register_{lint_group}.rs"),
+ update_mode,
+ &content,
+ );
+ }
+
+ 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);
+}
+
+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!(" {}", lint);
++ println!("{header}");
+ for lint in lints.iter().sorted() {
- println!("\n## {}", lint_group);
++ println!(" {lint}");
+ }
+ println!();
+ true
+}
+
+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!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc);
++ println!("\n## {lint_group}");
+
+ lints.sort_by_key(|l| l.name.clone());
+
+ for lint in lints {
- println!("there are {} lints", usable_lint_count);
++ println!("* [{}]({DOCS_LINK}#{}) ({})", lint.name, lint.name, lint.desc);
+ }
+ }
+
- panic!("`{}` should not contain the `{}` prefix", old_name, prefix);
++ 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!("`{}` should not contain the `{}` prefix", new_name, prefix);
++ panic!("`{old_name}` should not contain the `{prefix}` prefix");
+ }
+ if let Some((prefix, _)) = new_name.split_once("::") {
- let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{}`", old_name));
++ 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;
+ }
+ }
- old_name: format!("clippy::{}", old_name),
++ let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{old_name}`"));
+
+ let lint = RenamedLint {
- format!("clippy::{}", new_name)
++ old_name: format!("clippy::{old_name}"),
+ new_name: if uplift {
+ new_name.into()
+ } else {
- "`{}` has already been renamed",
- old_name
++ 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),
- "`{}` has already been deprecated",
- old_name
++ "`{old_name}` has already been renamed"
+ );
+ assert!(
+ !deprecated_lints.iter().any(|l| lint.old_name == l.name),
- "`{}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually.",
- old_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!(
- "`{}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually.",
- new_name
++ "`{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!(
- Path::new(&format!("tests/ui/{}.rs", old_name)),
- Path::new(&format!("tests/ui/{}.rs", new_name)),
++ "`{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/{}.stderr", old_name)),
- Path::new(&format!("tests/ui/{}.stderr", new_name)),
++ Path::new(&format!("tests/ui/{old_name}.rs")),
++ Path::new(&format!("tests/ui/{new_name}.rs")),
+ ) {
+ try_rename_file(
- Path::new(&format!("tests/ui/{}.fixed", old_name)),
- Path::new(&format!("tests/ui/{}.fixed", new_name)),
++ Path::new(&format!("tests/ui/{old_name}.stderr")),
++ Path::new(&format!("tests/ui/{new_name}.stderr")),
+ );
+ try_rename_file(
- Path::new(&format!("clippy_lints/src/{}.rs", old_name)),
- Path::new(&format!("clippy_lints/src/{}.rs", new_name)),
++ 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/{}/{}.rs", lint.module, old_name)),
- Path::new(&format!("clippy_lints/src/{}/{}.rs", lint.module, new_name)),
++ 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(
- let renamed_mod = format!("{}::{}", lint.module, old_name);
++ 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.
- lint.module = format!("{}::{}", lint.module, new_name);
++ let renamed_mod = format!("{}::{old_name}", lint.module);
+ for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
- println!("{} has been successfully renamed", old_name);
++ 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!("info: `{}` has successfully been deprecated", name);
++ 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);
- let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{}`", name); return; };
++ 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 test_file_stem = format!("tests/ui/{}", name);
++ 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) {
- "warn: you will have to manually remove any code related to `{}` from `{}`",
- name,
++ 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!(
- let mod_decl = format!("\nmod {};", name);
++ "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;)
- fs::read_to_string(&path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.as_ref().display(), e));
++ 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 =
- .unwrap_or_else(|e| panic!("Cannot write to {}: {}", path.as_ref().display(), e));
++ 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())
- "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![",
- group_name
++ .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>,
+ documentation: String,
+}
+
+impl Lint {
+ #[must_use]
+ fn new(
+ name: &str,
+ group: &str,
+ desc: &str,
+ module: &str,
+ declaration_range: Range<usize>,
+ documentation: String,
+ ) -> Self {
+ Self {
+ name: name.to_lowercase(),
+ group: group.into(),
+ desc: remove_line_splices(desc),
+ module: module.into(),
+ declaration_range,
+ documentation,
+ }
+ }
+
+ /// 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),
+ }
+ }
+}
+
+/// 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,
- let _ = writeln!(output, " LintId::of({}::{}),", module, name);
++ "store.register_group(true, \"clippy::{group_name}\", Some(\"clippy_{group_name}\"), vec![",
+ );
+ for (module, name) in details {
- let _ = writeln!(output, " {}::{},", module_name, lint_name);
++ let _ = writeln!(output, " LintId::of({module}::{name}),");
+ }
+ output.push_str("])\n");
+
+ output
+}
+
+/// 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]
+fn gen_register_lint_list<'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();
+ output.push_str("store.register_lints(&[\n");
+
+ for (is_public, module_name, lint_name) in details {
+ if !is_public {
+ output.push_str(" #[cfg(feature = \"internal\")]\n");
+ }
- fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
++ let _ = writeln!(output, " {module_name}::{lint_name},");
+ }
+ 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 =
- .unwrap_or_else(|| panic!("expected quoted string, found `{}`", s));
++ 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))?)*) => {
+ {
+ $($(let $capture =)? if let Some(LintDeclSearchResult {
+ token_kind: TokenKind::$token $({$($fields)*})?,
+ content: _x,
+ ..
+ }) = $iter.next() {
+ _x
+ } 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 docs = String::with_capacity(128);
+ let mut iter = iter.by_ref().filter(|t| !matches!(t.token_kind, TokenKind::Whitespace));
+ // matches `!{`
+ match_tokens!(iter, Bang OpenBrace);
+ 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;
+ },
+ _ => {},
+ }
+ }
+ docs.pop(); // remove final newline
+
+ 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()
+ {
+ lints.push(Lint::new(name, group, desc, module, start..range.end, docs));
+ }
+ }
+}
+
+/// 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('"'))
- let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
++ .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),
+) {
- Err(delim) => panic!("Couldn't find `{}` in file `{}`", delim, path.display()),
++ 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,
- panic!("Cannot write to `{}`: {}", path.display(), e);
++ 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!("failed to {} file `{}`: {}", action, name.display(), error)
++ 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(),
+ String::new(),
+ )];
+ assert_eq!(expected, Lint::usable_lints(&lints));
+ }
+
+ #[test]
+ fn test_by_lint_group() {
+ let lints = vec![
+ Lint::new(
+ "should_assert_eq",
+ "group1",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
+ String::new(),
+ ),
+ Lint::new(
+ "should_assert_eq2",
+ "group2",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
+ String::new(),
+ ),
+ Lint::new(
+ "incorrect_match",
+ "group1",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
+ String::new(),
+ ),
+ ];
+ let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
+ expected.insert(
+ "group1".to_string(),
+ vec![
+ 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(),
+ ),
+ ],
+ );
+ expected.insert(
+ "group2".to_string(),
+ vec![Lint::new(
+ "should_assert_eq2",
+ "group2",
+ "\"abc\"",
+ "module_name",
+ Range::default(),
+ String::new(),
+ )],
+ );
+ 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));
+ }
+
+ #[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);
+ }
+}
--- /dev/null
- version = "0.1.65"
+[package]
+name = "clippy_lints"
++version = "0.1.66"
+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" }
+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
- &format!("approximate value of `{}::consts::{}` found", module, &name),
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol;
+use std::f64::consts as f64;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for floating point literals that approximate
+ /// constants which are defined in
+ /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants)
+ /// or
+ /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants),
+ /// respectively, suggesting to use the predefined constant.
+ ///
+ /// ### Why is this bad?
+ /// Usually, the definition in the standard library is more
+ /// precise than what people come up with. If you find that your definition is
+ /// actually more precise, please [file a Rust
+ /// issue](https://github.com/rust-lang/rust/issues).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 3.14;
+ /// let y = 1_f64 / x;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = std::f32::consts::PI;
+ /// let y = std::f64::consts::FRAC_1_PI;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub APPROX_CONSTANT,
+ correctness,
+ "the approximate of a known float constant (in `std::fXX::consts`)"
+}
+
+// Tuples are of the form (constant, name, min_digits, msrv)
+const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
+ (f64::E, "E", 4, None),
+ (f64::FRAC_1_PI, "FRAC_1_PI", 4, None),
+ (f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5, None),
+ (f64::FRAC_2_PI, "FRAC_2_PI", 5, None),
+ (f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5, None),
+ (f64::FRAC_PI_2, "FRAC_PI_2", 5, None),
+ (f64::FRAC_PI_3, "FRAC_PI_3", 5, None),
+ (f64::FRAC_PI_4, "FRAC_PI_4", 5, None),
+ (f64::FRAC_PI_6, "FRAC_PI_6", 5, None),
+ (f64::FRAC_PI_8, "FRAC_PI_8", 5, None),
+ (f64::LN_2, "LN_2", 5, None),
+ (f64::LN_10, "LN_10", 5, None),
+ (f64::LOG2_10, "LOG2_10", 5, Some(msrvs::LOG2_10)),
+ (f64::LOG2_E, "LOG2_E", 5, None),
+ (f64::LOG10_2, "LOG10_2", 5, Some(msrvs::LOG10_2)),
+ (f64::LOG10_E, "LOG10_E", 5, None),
+ (f64::PI, "PI", 3, None),
+ (f64::SQRT_2, "SQRT_2", 5, None),
+ (f64::TAU, "TAU", 3, Some(msrvs::TAU)),
+];
+
+pub struct ApproxConstant {
+ msrv: Option<RustcVersion>,
+}
+
+impl ApproxConstant {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+
+ fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) {
+ match *lit {
+ LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
+ FloatTy::F32 => self.check_known_consts(cx, e, s, "f32"),
+ FloatTy::F64 => self.check_known_consts(cx, e, s, "f64"),
+ },
+ LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"),
+ _ => (),
+ }
+ }
+
+ fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
+ let s = s.as_str();
+ if s.parse::<f64>().is_ok() {
+ for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
+ if is_approx_const(constant, s, min_digits) && msrv.map_or(true, |msrv| meets_msrv(self.msrv, msrv)) {
+ span_lint_and_help(
+ cx,
+ APPROX_CONSTANT,
+ e.span,
- let round_const = format!("{:.*}", value.len() - 2, constant);
++ &format!("approximate value of `{module}::consts::{}` found", &name),
+ None,
+ "consider using the constant directly",
+ );
+ return;
+ }
+ }
+ }
+ }
+}
+
+impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
+
+impl<'tcx> LateLintPass<'tcx> for ApproxConstant {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Lit(lit) = &e.kind {
+ self.check_lit(cx, &lit.node, e);
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Returns `false` if the number of significant figures in `value` are
+/// less than `min_digits`; otherwise, returns true if `value` is equal
+/// to `constant`, rounded to the number of digits present in `value`.
+#[must_use]
+fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool {
+ if value.len() <= min_digits {
+ false
+ } else if constant.to_string().starts_with(value) {
+ // The value is a truncated constant
+ true
+ } else {
++ let round_const = format!("{constant:.*}", value.len() - 2);
+ value == round_const
+ }
+}
--- /dev/null
- &format!("{} x86 assembly syntax used", style),
+use std::fmt;
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions};
+use rustc_lint::{EarlyContext, EarlyLintPass, Lint};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum AsmStyle {
+ Intel,
+ Att,
+}
+
+impl fmt::Display for AsmStyle {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ AsmStyle::Intel => f.write_str("Intel"),
+ AsmStyle::Att => f.write_str("AT&T"),
+ }
+ }
+}
+
+impl std::ops::Not for AsmStyle {
+ type Output = AsmStyle;
+
+ fn not(self) -> AsmStyle {
+ match self {
+ AsmStyle::Intel => AsmStyle::Att,
+ AsmStyle::Att => AsmStyle::Intel,
+ }
+ }
+}
+
+fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr, check_for: AsmStyle) {
+ if let ExprKind::InlineAsm(ref inline_asm) = expr.kind {
+ let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) {
+ AsmStyle::Att
+ } else {
+ AsmStyle::Intel
+ };
+
+ if style == check_for {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
++ &format!("{style} x86 assembly syntax used"),
+ None,
+ &format!("use {} x86 assembly syntax", !style),
+ );
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of Intel x86 assembly syntax.
+ ///
+ /// ### Why is this bad?
+ /// The lint has been enabled to indicate a preference
+ /// for AT&T x86 assembly syntax.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// # #![feature(asm)]
++ /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// # use std::arch::asm;
+ /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr);
+ /// # }
+ /// ```
+ /// Use instead:
+ /// ```rust,no_run
+ /// # #![feature(asm)]
++ /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// # use std::arch::asm;
+ /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax));
+ /// # }
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub INLINE_ASM_X86_INTEL_SYNTAX,
+ restriction,
+ "prefer AT&T x86 assembly syntax"
+}
+
+declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]);
+
+impl EarlyLintPass for InlineAsmX86IntelSyntax {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Intel);
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of AT&T x86 assembly syntax.
+ ///
+ /// ### Why is this bad?
+ /// The lint has been enabled to indicate a preference
+ /// for Intel x86 assembly syntax.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// # #![feature(asm)]
++ /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// # use std::arch::asm;
+ /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax));
+ /// # }
+ /// ```
+ /// Use instead:
+ /// ```rust,no_run
+ /// # #![feature(asm)]
++ /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// # use std::arch::asm;
+ /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr);
+ /// # }
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub INLINE_ASM_X86_ATT_SYNTAX,
+ restriction,
+ "prefer Intel x86 assembly syntax"
+}
+
+declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]);
+
+impl EarlyLintPass for InlineAsmX86AttSyntax {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Att);
+ }
+}
--- /dev/null
- &format!("`assert!(false{})` should probably be replaced", assert_arg),
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
+use rustc_hir::Expr;
+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 `assert!(true)` and `assert!(false)` calls.
+ ///
+ /// ### Why is this bad?
+ /// Will be optimized out by the compiler or should probably be replaced by a
+ /// `panic!()` or `unreachable!()`
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// assert!(false)
+ /// assert!(true)
+ /// const B: bool = false;
+ /// assert!(B)
+ /// ```
+ #[clippy::version = "1.34.0"]
+ pub ASSERTIONS_ON_CONSTANTS,
+ style,
+ "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`"
+}
+
+declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
+
+impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, e) else { return };
+ let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
+ Some(sym::debug_assert_macro) => true,
+ Some(sym::assert_macro) => false,
+ _ => return,
+ };
+ let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return };
+ let Some((Constant::Bool(val), _)) = constant(cx, cx.typeck_results(), condition) else { return };
+ if val {
+ span_lint_and_help(
+ cx,
+ ASSERTIONS_ON_CONSTANTS,
+ macro_call.span,
+ &format!(
+ "`{}!(true)` will be optimized out by the compiler",
+ cx.tcx.item_name(macro_call.def_id)
+ ),
+ None,
+ "remove it",
+ );
+ } else if !is_debug {
+ let (assert_arg, panic_arg) = match panic_expn {
+ PanicExpn::Empty => ("", ""),
+ _ => (", ..", ".."),
+ };
+ span_lint_and_help(
+ cx,
+ ASSERTIONS_ON_CONSTANTS,
+ macro_call.span,
- &format!("use `panic!({})` or `unreachable!({0})`", panic_arg),
++ &format!("`assert!(false{assert_arg})` should probably be replaced"),
+ None,
++ &format!("use `panic!({panic_arg})` or `unreachable!({panic_arg})`"),
+ );
+ }
+ }
+}
--- /dev/null
- "{}.unwrap(){}",
- snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0,
- semicolon
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item};
+use clippy_utils::usage::local_used_after_expr;
+use clippy_utils::{is_expr_final_block_expr, path_res};
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `assert!(r.is_ok())` or `assert!(r.is_err())` calls.
+ ///
+ /// ### Why is this bad?
+ /// An assertion failure cannot output an useful message of the error.
+ ///
+ /// ### Known problems
+ /// The suggested replacement decreases the readability of code and log output.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// # let r = Ok::<_, ()>(());
+ /// assert!(r.is_ok());
+ /// # let r = Err::<_, ()>(());
+ /// assert!(r.is_err());
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub ASSERTIONS_ON_RESULT_STATES,
+ restriction,
+ "`assert!(r.is_ok())`/`assert!(r.is_err())` gives worse error message than directly calling `r.unwrap()`/`r.unwrap_err()`"
+}
+
+declare_lint_pass!(AssertionsOnResultStates => [ASSERTIONS_ON_RESULT_STATES]);
+
+impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let Some(macro_call) = root_macro_call_first_node(cx, e)
+ && matches!(cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::assert_macro))
+ && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn)
+ && matches!(panic_expn, PanicExpn::Empty)
+ && let ExprKind::MethodCall(method_segment, recv, [], _) = condition.kind
+ && let result_type_with_refs = cx.typeck_results().expr_ty(recv)
+ && let result_type = result_type_with_refs.peel_refs()
+ && is_type_diagnostic_item(cx, result_type, sym::Result)
+ && let ty::Adt(_, substs) = result_type.kind()
+ {
+ if !is_copy(cx, result_type) {
+ if result_type_with_refs != result_type {
+ return;
+ } else if let Res::Local(binding_id) = path_res(cx, recv)
+ && local_used_after_expr(cx, binding_id, recv)
+ {
+ return;
+ }
+ }
+ let semicolon = if is_expr_final_block_expr(cx.tcx, e) {";"} else {""};
+ let mut app = Applicability::MachineApplicable;
+ match method_segment.ident.as_str() {
+ "is_ok" if type_suitable_to_unwrap(cx, substs.type_at(1)) => {
+ span_lint_and_sugg(
+ cx,
+ ASSERTIONS_ON_RESULT_STATES,
+ macro_call.span,
+ "called `assert!` with `Result::is_ok`",
+ "replace with",
+ format!(
- "{}.unwrap_err(){}",
- snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0,
- semicolon
++ "{}.unwrap(){semicolon}",
++ snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
+ ),
+ app,
+ );
+ }
+ "is_err" if type_suitable_to_unwrap(cx, substs.type_at(0)) => {
+ span_lint_and_sugg(
+ cx,
+ ASSERTIONS_ON_RESULT_STATES,
+ macro_call.span,
+ "called `assert!` with `Result::is_err`",
+ "replace with",
+ format!(
++ "{}.unwrap_err(){semicolon}",
++ snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
+ ),
+ app,
+ );
+ }
+ _ => (),
+ };
+ }
+ }
+}
+
+fn type_suitable_to_unwrap<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ has_debug_impl(cx, ty) && !ty.is_unit() && !ty.is_never()
+}
--- /dev/null
- &format!(
- "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
- name
- ),
+//! 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_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, 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;
+use rustc_span::sym;
+use rustc_span::symbol::Symbol;
+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_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",
+ )
+ })
+ {
+ 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(),
+ "restriction lints are not meant to be all enabled",
+ None,
+ "try enabling only the lints you really need",
+ );
+ }
+ }
+ }
+}
+
+fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
+ // Check for the feature
+ if !cx.tcx.sess.features_untracked().lint_reasons {
+ return;
+ }
+
+ // Check if the reason is present
+ if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
+ && let MetaItemKind::NameValue(_) = &item.kind
+ && item.path == sym::reason
+ {
+ return;
+ }
+
+ span_lint_and_help(
+ cx,
+ ALLOW_ATTRIBUTES_WITHOUT_REASON,
+ attr.span,
+ &format!("`{}` attribute without specifying a reason", name.as_str()),
+ None,
+ "try adding a reason at the end with `, reason = \"..\"`",
+ );
+}
+
+fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Fn(_, _, eid) = item.kind {
+ is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
+ } 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,
- let sugg = format!("target_os = \"{}\"", os);
++ &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::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{match_def_path, paths};
+use rustc_data_structures::fx::FxHashMap;
++use rustc_hir::def::{Namespace, Res};
+use rustc_hir::def_id::DefId;
- use rustc_span::Span;
++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 crate::utils::conf::DisallowedType;
++use rustc_span::{sym, Span};
+
- conf_invalid_types: Vec<DisallowedType>,
- def_ids: FxHashMap<DefId, DisallowedType>,
++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 {
- pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
++ conf_invalid_types: Vec<DisallowedPath>,
++ def_ids: FxHashMap<DefId, DisallowedPath>,
+}
+
+impl AwaitHolding {
- let path = match conf {
- DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path,
- };
- let segs: Vec<_> = path.split("::").collect();
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
++ 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 {
- fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) {
- let (type_name, reason) = match disallowed {
- DisallowedType::Simple(path) => (path, &None),
- DisallowedType::WithReason { path, reason } => (path, reason),
- };
-
++ let segs: Vec<_> = conf.path().split("::").collect();
++ if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
+ 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);
+ }
+ }
+ }
+ }
+}
+
- &format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
++fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPath) {
+ span_lint_and_then(
+ cx,
+ AWAIT_HOLDING_INVALID_TYPE,
+ span,
- if let Some(reason) = reason {
- diag.note(reason.clone());
++ &format!(
++ "`{}` may not be held across an `await` point per `clippy.toml`",
++ disallowed.path()
++ ),
+ |diag| {
- match_def_path(cx, def_id, &paths::MUTEX_GUARD)
- || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
- || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
++ 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 rustc_hir::intravisit::{walk_expr, Visitor};
- use rustc_hir::{BlockCheckMode, Closure, Expr, ExprKind};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::get_parent_expr;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_block_with_applicability;
+use clippy_utils::ty::implements_trait;
++use clippy_utils::visitors::{for_each_expr, Descend};
++use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- struct ExVisitor<'a, 'tcx> {
- found_block: Option<&'tcx Expr<'tcx>>,
- cx: &'a LateContext<'tcx>,
- }
-
- impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- if let ExprKind::Closure(&Closure { body, .. }) = expr.kind {
- // do not lint if the closure is called using an iterator (see #1141)
- if_chain! {
- if let Some(parent) = get_parent_expr(self.cx, expr);
- if let ExprKind::MethodCall(_, self_arg, ..) = &parent.kind;
- let caller = self.cx.typeck_results().expr_ty(self_arg);
- if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator);
- if implements_trait(self.cx, caller, iter_id, &[]);
- then {
- return;
- }
- }
-
- let body = self.cx.tcx.hir().body(body);
- let ex = &body.value;
- if let ExprKind::Block(block, _) = ex.kind {
- if !body.value.span.from_expansion() && !block.stmts.is_empty() {
- self.found_block = Some(ex);
- return;
- }
- }
- }
- walk_expr(self, expr);
- }
- }
-
++use rustc_hir::{BlockCheckMode, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `if` conditions that use blocks containing an
+ /// expression, statements or conditions that use closures with blocks.
+ ///
+ /// ### Why is this bad?
+ /// Style, using blocks in the condition makes it hard to read.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # fn somefunc() -> bool { true };
+ /// if { true } { /* ... */ }
+ ///
+ /// if { let x = somefunc(); x } { /* ... */ }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # fn somefunc() -> bool { true };
+ /// if true { /* ... */ }
+ ///
+ /// let res = { let x = somefunc(); x };
+ /// if res { /* ... */ }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub BLOCKS_IN_IF_CONDITIONS,
+ style,
+ "useless or complex blocks that can be eliminated in conditions"
+}
+
+declare_lint_pass!(BlocksInIfConditions => [BLOCKS_IN_IF_CONDITIONS]);
+
- let mut visitor = ExVisitor { found_block: None, cx };
- walk_expr(&mut visitor, cond);
- if let Some(block) = visitor.found_block {
- span_lint(cx, BLOCKS_IN_IF_CONDITIONS, block.span, COMPLEX_BLOCK_MESSAGE);
- }
+const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
+const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \
+ instead, move the block or closure higher and bind it with a `let`";
+
+impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+ if let Some(higher::If { cond, .. }) = higher::If::hir(expr) {
+ if let ExprKind::Block(block, _) = &cond.kind {
+ if block.rules == BlockCheckMode::DefaultBlock {
+ if block.stmts.is_empty() {
+ if let Some(ex) = &block.expr {
+ // don't dig into the expression here, just suggest that they remove
+ // the block
+ if expr.span.from_expansion() || ex.span.from_expansion() {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BLOCKS_IN_IF_CONDITIONS,
+ cond.span,
+ BRACED_EXPR_MESSAGE,
+ "try",
+ format!(
+ "{}",
+ snippet_block_with_applicability(
+ cx,
+ ex.span,
+ "..",
+ Some(expr.span),
+ &mut applicability
+ )
+ ),
+ applicability,
+ );
+ }
+ } else {
+ let span =
+ block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
+ if span.from_expansion() || expr.span.from_expansion() {
+ return;
+ }
+ // move block higher
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BLOCKS_IN_IF_CONDITIONS,
+ expr.span.with_hi(cond.span.hi()),
+ COMPLEX_BLOCK_MESSAGE,
+ "try",
+ format!(
+ "let res = {}; if res",
+ snippet_block_with_applicability(
+ cx,
+ block.span,
+ "..",
+ Some(expr.span),
+ &mut applicability
+ ),
+ ),
+ applicability,
+ );
+ }
+ }
+ } else {
++ let _: Option<!> = for_each_expr(cond, |e| {
++ if let ExprKind::Closure(closure) = e.kind {
++ // do not lint if the closure is called using an iterator (see #1141)
++ if_chain! {
++ if let Some(parent) = get_parent_expr(cx, e);
++ if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind;
++ let caller = cx.typeck_results().expr_ty(self_arg);
++ if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
++ if implements_trait(cx, caller, iter_id, &[]);
++ then {
++ return ControlFlow::Continue(Descend::No);
++ }
++ }
++
++ let body = cx.tcx.hir().body(closure.body);
++ let ex = &body.value;
++ if let ExprKind::Block(block, _) = ex.kind {
++ if !body.value.span.from_expansion() && !block.stmts.is_empty() {
++ span_lint(cx, BLOCKS_IN_IF_CONDITIONS, ex.span, COMPLEX_BLOCK_MESSAGE);
++ return ControlFlow::Continue(Descend::No);
++ }
++ }
++ }
++ ControlFlow::Continue(Descend::Yes)
++ });
+ }
+ }
+ }
+}
--- /dev/null
- &format!("used `{}!` with a literal bool", macro_name),
+use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
+use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Lit};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about boolean comparisons in assert-like macros.
+ ///
+ /// ### Why is this bad?
+ /// It is shorter to use the equivalent.
+ ///
+ /// ### Example
+ /// ```rust
+ /// assert_eq!("a".is_empty(), false);
+ /// assert_ne!("a".is_empty(), true);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// assert!(!"a".is_empty());
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub BOOL_ASSERT_COMPARISON,
+ style,
+ "Using a boolean as comparison value in an assert_* macro when there is no need"
+}
+
+declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]);
+
+fn is_bool_lit(e: &Expr<'_>) -> bool {
+ matches!(
+ e.kind,
+ ExprKind::Lit(Lit {
+ node: LitKind::Bool(_),
+ ..
+ })
+ ) && !e.span.from_expansion()
+}
+
+fn is_impl_not_trait_with_bool_out(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(e);
+
+ cx.tcx
+ .lang_items()
+ .not_trait()
+ .filter(|trait_id| implements_trait(cx, ty, *trait_id, &[]))
+ .and_then(|trait_id| {
+ cx.tcx.associated_items(trait_id).find_by_name_and_kind(
+ cx.tcx,
+ Ident::from_str("Output"),
+ ty::AssocKind::Type,
+ trait_id,
+ )
+ })
+ .map_or(false, |assoc_item| {
+ let proj = cx.tcx.mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(ty, &[]));
+ let nty = cx.tcx.normalize_erasing_regions(cx.param_env, proj);
+
+ nty.is_bool()
+ })
+}
+
+impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
+ let macro_name = cx.tcx.item_name(macro_call.def_id);
+ if !matches!(
+ macro_name.as_str(),
+ "assert_eq" | "debug_assert_eq" | "assert_ne" | "debug_assert_ne"
+ ) {
+ return;
+ }
+ let Some ((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
+ if !(is_bool_lit(a) ^ is_bool_lit(b)) {
+ // If there are two boolean arguments, we definitely don't understand
+ // what's going on, so better leave things as is...
+ //
+ // Or there is simply no boolean and then we can leave things as is!
+ return;
+ }
+
+ if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
+ // At this point the expression which is not a boolean
+ // literal does not implement Not trait with a bool output,
+ // so we cannot suggest to rewrite our code
+ return;
+ }
+
+ let macro_name = macro_name.as_str();
+ let non_eq_mac = ¯o_name[..macro_name.len() - 3];
+ span_lint_and_sugg(
+ cx,
+ BOOL_ASSERT_COMPARISON,
+ macro_call.span,
- format!("{}!(..)", non_eq_mac),
++ &format!("used `{macro_name}!` with a literal bool"),
+ "replace it with",
++ format!("{non_eq_mac}!(..)"),
+ Applicability::MaybeIncorrect,
+ );
+ }
+}
--- /dev/null
- use clippy_utils::{diagnostics::span_lint_and_then, is_else_clause, sugg::Sugg};
+use rustc_ast::LitKind;
+use rustc_hir::{Block, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
- let inverted = if
- check_int_literal_equals_val(then_lit, 1)
- && check_int_literal_equals_val(else_lit, 0) {
++use clippy_utils::{diagnostics::span_lint_and_then, 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?
+ /// Coercion or `from()` is idiomatic 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,
+ style,
+ "using if to convert bool to int"
+}
+declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
+
+impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
+ fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
+ if !expr.span.from_expansion() {
+ check_if_else(ctx, expr);
+ }
+ }
+}
+
+fn check_if_else<'tcx>(ctx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
+ if let ExprKind::If(check, then, Some(else_)) = expr.kind
+ && let Some(then_lit) = int_literal(then)
+ && let Some(else_lit) = int_literal(else_)
+ {
- } else if
- check_int_literal_equals_val(then_lit, 0)
- && check_int_literal_equals_val(else_lit, 1) {
++ let inverted = if is_integer_literal(then_lit, 1) && is_integer_literal(else_lit, 0) {
+ false
-
- fn check_int_literal_equals_val<'tcx>(expr: &'tcx rustc_hir::Expr<'tcx>, expected_value: u128) -> bool {
- if let ExprKind::Lit(lit) = &expr.kind
- && let LitKind::Int(val, _) = lit.node
- && val == expected_value
- {
- true
- } else {
- 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 mut sugg = Sugg::hir_with_applicability(ctx, check, "..", &mut applicability);
+ if inverted {
+ sugg = !sugg;
+ }
+ sugg
+ };
+
+ let ty = ctx.typeck_results().expr_ty(then_lit); // then and else must be of same type
+
+ let suggestion = {
+ let wrap_in_curly = is_else_clause(ctx.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(ctx,
+ 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
- "{}{}{}",
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
+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,
++ "{}{op}{}",
+ snippet_opt(cx, lhs.span)?,
- .and_then(|(_, neg_method)| Some(format!("{}.{}()", snippet_opt(cx, receiver.span)?, neg_method)))
+ 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<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ get_trait_def_id(cx, &paths::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
--- /dev/null
++use clippy_utils::{diagnostics::span_lint_and_help, is_default_equivalent, path_def_id};
++use rustc_hir::{Expr, ExprKind, QPath};
++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::sym;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// checks for `Box::new(T::default())`, which is better written as
++ /// `Box::<T>::default()`.
++ ///
++ /// ### Why is this bad?
++ /// First, it's more complex, involving two calls instead of one.
++ /// Second, `Box::default()` can be faster
++ /// [in certain cases](https://nnethercote.github.io/perf-book/standard-library-types.html#box).
++ ///
++ /// ### Known problems
++ /// The lint may miss some cases (e.g. Box::new(String::from(""))).
++ /// On the other hand, it will trigger on cases where the `default`
++ /// code comes from a macro that does something different based on
++ /// e.g. target operating system.
++ ///
++ /// ### Example
++ /// ```rust
++ /// let x: Box<String> = Box::new(Default::default());
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// let x: Box<String> = Box::default();
++ /// ```
++ #[clippy::version = "1.65.0"]
++ pub BOX_DEFAULT,
++ perf,
++ "Using Box::new(T::default()) instead of Box::default()"
++}
++
++declare_lint_pass!(BoxDefault => [BOX_DEFAULT]);
++
++impl LateLintPass<'_> for BoxDefault {
++ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
++ if let ExprKind::Call(box_new, [arg]) = expr.kind
++ && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind
++ && let ExprKind::Call(..) = arg.kind
++ && !in_external_macro(cx.sess(), expr.span)
++ && expr.span.eq_ctxt(arg.span)
++ && seg.ident.name == sym::new
++ && path_def_id(cx, ty) == cx.tcx.lang_items().owned_box()
++ && is_default_equivalent(cx, arg)
++ {
++ span_lint_and_help(
++ cx,
++ BOX_DEFAULT,
++ expr.span,
++ "`Box::new(_)` of default value",
++ None,
++ "use `Box::default()` instead",
++ );
++ }
++ }
++}
--- /dev/null
- let message = format!("package `{}` is missing `{}` metadata", package.name, field);
+//! lint on missing cargo common metadata
+
+use cargo_metadata::Metadata;
+use clippy_utils::diagnostics::span_lint;
+use rustc_lint::LateContext;
+use rustc_span::source_map::DUMMY_SP;
+
+use super::CARGO_COMMON_METADATA;
+
+pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, ignore_publish: bool) {
+ for package in &metadata.packages {
+ // only run the lint if publish is `None` (`publish = true` or skipped entirely)
+ // or if the vector isn't empty (`publish = ["something"]`)
+ if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || ignore_publish {
+ if is_empty_str(&package.description) {
+ missing_warning(cx, package, "package.description");
+ }
+
+ if is_empty_str(&package.license) && is_empty_str(&package.license_file) {
+ missing_warning(cx, package, "either package.license or package.license_file");
+ }
+
+ if is_empty_str(&package.repository) {
+ missing_warning(cx, package, "package.repository");
+ }
+
+ if is_empty_str(&package.readme) {
+ missing_warning(cx, package, "package.readme");
+ }
+
+ if is_empty_vec(&package.keywords) {
+ missing_warning(cx, package, "package.keywords");
+ }
+
+ if is_empty_vec(&package.categories) {
+ missing_warning(cx, package, "package.categories");
+ }
+ }
+ }
+}
+
+fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) {
++ let message = format!("package `{}` is missing `{field}` metadata", package.name);
+ span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message);
+}
+
+fn is_empty_str<T: AsRef<std::ffi::OsStr>>(value: &Option<T>) -> bool {
+ value.as_ref().map_or(true, |s| s.as_ref().is_empty())
+}
+
+fn is_empty_vec(value: &[String]) -> bool {
+ // This works because empty iterators return true
+ value.iter().all(String::is_empty)
+}
--- /dev/null
- "the \"{}\" {} in the feature name \"{}\" is {}",
- substring,
+use cargo_metadata::Metadata;
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_lint::LateContext;
+use rustc_span::source_map::DUMMY_SP;
+
+use super::{NEGATIVE_FEATURE_NAMES, REDUNDANT_FEATURE_NAMES};
+
+static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
+static SUFFIXES: [&str; 2] = ["-support", "_support"];
+
+pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
+ for package in &metadata.packages {
+ let mut features: Vec<&String> = package.features.keys().collect();
+ features.sort();
+ for feature in features {
+ let prefix_opt = {
+ let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
+ if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
+ Some(PREFIXES[i - 1])
+ } else {
+ None
+ }
+ };
+ if let Some(prefix) = prefix_opt {
+ lint(cx, feature, prefix, true);
+ }
+
+ let suffix_opt: Option<&str> = {
+ let i = SUFFIXES.partition_point(|suffix| {
+ suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
+ });
+ if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
+ Some(SUFFIXES[i - 1])
+ } else {
+ None
+ }
+ };
+ if let Some(suffix) = suffix_opt {
+ lint(cx, feature, suffix, false);
+ }
+ }
+ }
+}
+
+fn is_negative_prefix(s: &str) -> bool {
+ s.starts_with("no")
+}
+
+fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
+ let is_negative = is_prefix && is_negative_prefix(substring);
+ span_lint_and_help(
+ cx,
+ if is_negative {
+ NEGATIVE_FEATURE_NAMES
+ } else {
+ REDUNDANT_FEATURE_NAMES
+ },
+ DUMMY_SP,
+ &format!(
- feature,
++ "the \"{substring}\" {} in the feature name \"{feature}\" is {}",
+ if is_prefix { "prefix" } else { "suffix" },
+ if is_negative { "negative" } else { "redundant" }
+ ),
+ None,
+ &format!(
+ "consider renaming the feature to \"{}\"{}",
+ if is_prefix {
+ feature.strip_prefix(substring)
+ } else {
+ feature.strip_suffix(substring)
+ }
+ .unwrap(),
+ if is_negative {
+ ", but make sure the feature adds functionality"
+ } else {
+ ""
+ }
+ ),
+ );
+}
+
+#[test]
+fn test_prefixes_sorted() {
+ let mut sorted_prefixes = PREFIXES;
+ sorted_prefixes.sort_unstable();
+ assert_eq!(PREFIXES, sorted_prefixes);
+ let mut sorted_suffixes = SUFFIXES;
+ sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
+ assert_eq!(SUFFIXES, sorted_suffixes);
+}
--- /dev/null
- span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e));
+mod common_metadata;
+mod feature_name;
+mod multiple_crate_versions;
+mod wildcard_dependencies;
+
+use cargo_metadata::MetadataCommand;
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_lint_allowed;
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_lint::{LateContext, LateLintPass, Lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::DUMMY_SP;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks to see if all common metadata is defined in
+ /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata
+ ///
+ /// ### Why is this bad?
+ /// It will be more difficult for users to discover the
+ /// purpose of the crate, and key information related to it.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # This `Cargo.toml` is missing a description field:
+ /// [package]
+ /// name = "clippy"
+ /// version = "0.0.212"
+ /// 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"]
+ /// ```
+ ///
+ /// Should include a description field like:
+ ///
+ /// ```toml
+ /// # This `Cargo.toml` includes all common metadata
+ /// [package]
+ /// name = "clippy"
+ /// version = "0.0.212"
+ /// 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"]
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub CARGO_COMMON_METADATA,
+ cargo,
+ "common metadata is defined in `Cargo.toml`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
+ ///
+ /// ### Why is this bad?
+ /// These prefixes and suffixes have no significant meaning.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # The `Cargo.toml` with feature name redundancy
+ /// [features]
+ /// default = ["use-abc", "with-def", "ghi-support"]
+ /// use-abc = [] // redundant
+ /// with-def = [] // redundant
+ /// ghi-support = [] // redundant
+ /// ```
+ ///
+ /// Use instead:
+ /// ```toml
+ /// [features]
+ /// default = ["abc", "def", "ghi"]
+ /// abc = []
+ /// def = []
+ /// ghi = []
+ /// ```
+ ///
+ #[clippy::version = "1.57.0"]
+ pub REDUNDANT_FEATURE_NAMES,
+ cargo,
+ "usage of a redundant feature name"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for negative feature names with prefix `no-` or `not-`
+ ///
+ /// ### Why is this bad?
+ /// Features are supposed to be additive, and negatively-named features violate it.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # The `Cargo.toml` with negative feature names
+ /// [features]
+ /// default = []
+ /// no-abc = []
+ /// not-def = []
+ ///
+ /// ```
+ /// Use instead:
+ /// ```toml
+ /// [features]
+ /// default = ["abc", "def"]
+ /// abc = []
+ /// def = []
+ ///
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub NEGATIVE_FEATURE_NAMES,
+ cargo,
+ "usage of a negative feature name"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks to see if multiple versions of a crate are being
+ /// used.
+ ///
+ /// ### Why is this bad?
+ /// This bloats the size of targets, and can lead to
+ /// confusing error messages when structs or traits are used interchangeably
+ /// between different versions of a crate.
+ ///
+ /// ### Known problems
+ /// Because this can be caused purely by the dependencies
+ /// themselves, it's not always possible to fix this issue.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning.
+ /// [dependencies]
+ /// ctrlc = "=3.1.0"
+ /// ansi_term = "=0.11.0"
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MULTIPLE_CRATE_VERSIONS,
+ cargo,
+ "multiple versions of the same crate being used"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard dependencies in the `Cargo.toml`.
+ ///
+ /// ### Why is this bad?
+ /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html),
+ /// it is highly unlikely that you work with any possible version of your dependency,
+ /// and wildcard dependencies would cause unnecessary breakage in the ecosystem.
+ ///
+ /// ### Example
+ /// ```toml
+ /// [dependencies]
+ /// regex = "*"
+ /// ```
+ #[clippy::version = "1.32.0"]
+ pub WILDCARD_DEPENDENCIES,
+ cargo,
+ "wildcard dependencies being used"
+}
+
+pub struct Cargo {
+ pub ignore_publish: bool,
+}
+
+impl_lint_pass!(Cargo => [
+ CARGO_COMMON_METADATA,
+ REDUNDANT_FEATURE_NAMES,
+ NEGATIVE_FEATURE_NAMES,
+ MULTIPLE_CRATE_VERSIONS,
+ WILDCARD_DEPENDENCIES
+]);
+
+impl LateLintPass<'_> for Cargo {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ static NO_DEPS_LINTS: &[&Lint] = &[
+ CARGO_COMMON_METADATA,
+ REDUNDANT_FEATURE_NAMES,
+ NEGATIVE_FEATURE_NAMES,
+ WILDCARD_DEPENDENCIES,
+ ];
+ static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
+
+ if !NO_DEPS_LINTS
+ .iter()
+ .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
+ {
+ match MetadataCommand::new().no_deps().exec() {
+ Ok(metadata) => {
+ common_metadata::check(cx, &metadata, self.ignore_publish);
+ feature_name::check(cx, &metadata);
+ wildcard_dependencies::check(cx, &metadata);
+ },
+ Err(e) => {
+ for lint in NO_DEPS_LINTS {
- span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e));
++ span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {e}"));
+ }
+ },
+ }
+ }
+
+ if !WITH_DEPS_LINTS
+ .iter()
+ .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
+ {
+ match MetadataCommand::new().exec() {
+ Ok(metadata) => {
+ multiple_crate_versions::check(cx, &metadata);
+ },
+ Err(e) => {
+ for lint in WITH_DEPS_LINTS {
++ span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {e}"));
+ }
+ },
+ }
+ }
+ }
+}
--- /dev/null
- &format!("multiple versions for dependency `{}`: {}", name, versions),
+//! lint on multiple versions of a crate being used
+
+use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
+use clippy_utils::diagnostics::span_lint;
+use if_chain::if_chain;
+use itertools::Itertools;
+use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_lint::LateContext;
+use rustc_span::source_map::DUMMY_SP;
+
+use super::MULTIPLE_CRATE_VERSIONS;
+
+pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
+ let local_name = cx.tcx.crate_name(LOCAL_CRATE);
+ let mut packages = metadata.packages.clone();
+ packages.sort_by(|a, b| a.name.cmp(&b.name));
+
+ if_chain! {
+ if let Some(resolve) = &metadata.resolve;
+ if let Some(local_id) = packages
+ .iter()
+ .find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None });
+ then {
+ for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
+ let group: Vec<&Package> = group.collect();
+
+ if group.len() <= 1 {
+ continue;
+ }
+
+ if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
+ let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
+ versions.sort();
+ let versions = versions.iter().join(", ");
+
+ span_lint(
+ cx,
+ MULTIPLE_CRATE_VERSIONS,
+ DUMMY_SP,
++ &format!("multiple versions for dependency `{name}`: {versions}"),
+ );
+ }
+ }
+ }
+ }
+}
+
+fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool {
+ fn depends_on(node: &Node, dep_id: &PackageId) -> bool {
+ node.deps.iter().any(|dep| {
+ dep.pkg == *dep_id
+ && dep
+ .dep_kinds
+ .iter()
+ .any(|info| matches!(info.kind, DependencyKind::Normal))
+ })
+ }
+
+ nodes
+ .iter()
+ .filter(|node| depends_on(node, dep_id))
+ .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id))
+}
--- /dev/null
- format!("{}::ptr::{}!({})", core_or_std, macro_name, snip),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_no_std_crate;
+use clippy_utils::source::snippet_with_context;
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
+use rustc_lint::LateContext;
+
+use super::BORROW_AS_PTR;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ cast_expr: &'tcx Expr<'_>,
+ cast_to: &'tcx Ty<'_>,
+) {
+ if matches!(cast_to.kind, TyKind::Ptr(_))
+ && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
+ {
+ let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
+ let macro_name = match mutability {
+ Mutability::Not => "addr_of",
+ Mutability::Mut => "addr_of_mut",
+ };
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
+
+ span_lint_and_sugg(
+ cx,
+ BORROW_AS_PTR,
+ expr.span,
+ "borrow as raw pointer",
+ "try",
++ format!("{core_or_std}::ptr::{macro_name}!({snip})"),
+ Applicability::MachineApplicable,
+ );
+ }
+}
--- /dev/null
- format!(
- "casting `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`",
- cast_from, cast_to
- )
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_isize_or_usize;
+use clippy_utils::{in_constant, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, FloatTy, Ty};
+use rustc_semver::RustcVersion;
+
+use super::{utils, CAST_LOSSLESS};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_op: &Expr<'_>,
+ cast_from: Ty<'_>,
+ cast_to: Ty<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if !should_lint(cx, expr, cast_from, cast_to, msrv) {
+ return;
+ }
+
+ // The suggestion is to use a function call, so if the original expression
+ // has parens on the outside, they are no longer needed.
+ let mut applicability = Applicability::MachineApplicable;
+ let opt = snippet_opt(cx, cast_op.span);
+ let sugg = opt.as_ref().map_or_else(
+ || {
+ applicability = Applicability::HasPlaceholders;
+ ".."
+ },
+ |snip| {
+ if should_strip_parens(cast_op, snip) {
+ &snip[1..snip.len() - 1]
+ } else {
+ snip.as_str()
+ }
+ },
+ );
+
+ let message = if cast_from.is_bool() {
- format!(
- "casting `{}` to `{}` may become silently lossy if you later change the type",
- cast_from, cast_to
- )
++ format!("casting `{cast_from:}` to `{cast_to:}` is more cleanly stated with `{cast_to:}::from(_)`")
+ } else {
- format!("{}::from({})", cast_to, sugg),
++ format!("casting `{cast_from}` to `{cast_to}` may become silently lossy if you later change the type")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ CAST_LOSSLESS,
+ expr.span,
+ &message,
+ "try",
++ format!("{cast_to}::from({sugg})"),
+ applicability,
+ );
+}
+
+fn should_lint(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_from: Ty<'_>,
+ cast_to: Ty<'_>,
+ msrv: Option<RustcVersion>,
+) -> bool {
+ // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
+ if in_constant(cx, expr.hir_id) {
+ return false;
+ }
+
+ match (cast_from.is_integral(), cast_to.is_integral()) {
+ (true, true) => {
+ let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+ !is_isize_or_usize(cast_from)
+ && !is_isize_or_usize(cast_to)
+ && from_nbits < to_nbits
+ && !cast_signed_to_unsigned
+ },
+
+ (true, false) => {
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() {
+ 32
+ } else {
+ 64
+ };
+ !is_isize_or_usize(cast_from) && from_nbits < to_nbits
+ },
+ (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv, msrvs::FROM_BOOL) => true,
+ (_, _) => {
+ matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
+ },
+ }
+}
+
+fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool {
+ if let ExprKind::Binary(_, _, _) = cast_expr.kind {
+ if snip.starts_with('(') && snip.ends_with(')') {
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- format!(
- "casting `{}` to `{}` may truncate the value{}",
- cast_from, cast_to, suffix,
- )
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::expr_or_init;
+use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
+use rustc_ast::ast;
+use rustc_attr::IntType;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, FloatTy, Ty};
+
+use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
+
+fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
+ if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
+ Some(c)
+ } else {
+ None
+ }
+}
+
+fn get_constant_bits(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u64> {
+ constant_int(cx, expr).map(|c| u64::from(128 - c.leading_zeros()))
+}
+
+fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: bool) -> u64 {
+ match expr_or_init(cx, expr).kind {
+ ExprKind::Cast(inner, _) => apply_reductions(cx, nbits, inner, signed),
+ ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
+ ExprKind::Binary(op, left, right) => match op.node {
+ BinOpKind::Div => {
+ apply_reductions(cx, nbits, left, signed).saturating_sub(if signed {
+ // let's be conservative here
+ 0
+ } else {
+ // by dividing by 1, we remove 0 bits, etc.
+ get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
+ })
+ },
+ BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
+ .unwrap_or(u64::max_value())
+ .min(apply_reductions(cx, nbits, left, signed)),
+ BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
+ .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
+ _ => nbits,
+ },
+ ExprKind::MethodCall(method, left, [right], _) => {
+ if signed {
+ return nbits;
+ }
+ let max_bits = if method.ident.as_str() == "min" {
+ get_constant_bits(cx, right)
+ } else {
+ None
+ };
+ apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
+ },
+ ExprKind::MethodCall(method, _, [lo, hi], _) => {
+ if method.ident.as_str() == "clamp" {
+ //FIXME: make this a diagnostic item
+ if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
+ return lo_bits.max(hi_bits);
+ }
+ }
+ nbits
+ },
+ ExprKind::MethodCall(method, _value, [], _) => {
+ if method.ident.name.as_str() == "signum" {
+ 0 // do not lint if cast comes from a `signum` function
+ } else {
+ nbits
+ }
+ },
+ _ => nbits,
+ }
+}
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ let msg = match (cast_from.kind(), cast_to.is_integral()) {
+ (ty::Int(_) | ty::Uint(_), true) => {
+ let from_nbits = apply_reductions(
+ cx,
+ utils::int_ty_to_nbits(cast_from, cx.tcx),
+ cast_expr,
+ cast_from.is_signed(),
+ );
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+
+ let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
+ (true, true) | (false, false) => (to_nbits < from_nbits, ""),
+ (true, false) => (
+ to_nbits <= 32,
+ if to_nbits == 32 {
+ " on targets with 64-bit wide pointers"
+ } else {
+ ""
+ },
+ ),
+ (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"),
+ };
+
+ if !should_lint {
+ return;
+ }
+
- "casting `{}::{}` to `{}` will truncate the value{}",
- cast_from, variant.name, cast_to, suffix,
++ format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
+ },
+
+ (ty::Adt(def, _), true) if def.is_enum() => {
+ let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind
+ && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
+ {
+ let i = def.variant_index_with_ctor_id(id);
+ let variant = def.variant(i);
+ let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
+ (nbits, Some(variant))
+ } else {
+ (utils::enum_ty_to_nbits(*def, cx.tcx), None)
+ };
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+
+ let cast_from_ptr_size = def.repr().int.map_or(true, |ty| {
+ matches!(
+ ty,
+ IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
+ )
+ });
+ let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
+ (false, false) if from_nbits > to_nbits => "",
+ (true, false) if from_nbits > to_nbits => "",
+ (false, true) if from_nbits > 64 => "",
+ (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
+ _ => return,
+ };
+
+ if let Some(variant) = variant {
+ span_lint(
+ cx,
+ CAST_ENUM_TRUNCATION,
+ expr.span,
+ &format!(
- format!(
- "casting `{}` to `{}` may truncate the value{}",
- cast_from, cast_to, suffix,
- )
++ "casting `{cast_from}::{}` to `{cast_to}` will truncate the value{suffix}",
++ variant.name,
+ ),
+ );
+ return;
+ }
- format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
++ format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
+ },
+
+ (ty::Float(_), true) => {
++ format!("casting `{cast_from}` to `{cast_to}` may truncate the value")
+ },
+
+ (ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
+ "casting `f64` to `f32` may truncate the value".to_string()
+ },
+
+ _ => return,
+ };
+
+ span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
+}
--- /dev/null
- &format!(
- "casting `{}` to `{}` may wrap around the value{}",
- cast_from, cast_to, suffix,
- ),
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_isize_or_usize;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+
+use super::{utils, CAST_POSSIBLE_WRAP};
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ if !(cast_from.is_integral() && cast_to.is_integral()) {
+ return;
+ }
+
+ let arch_64_suffix = " on targets with 64-bit wide pointers";
+ let arch_32_suffix = " on targets with 32-bit wide pointers";
+ let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed();
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+
+ let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) {
+ (true, true) | (false, false) => (to_nbits == from_nbits && cast_unsigned_to_signed, ""),
+ (true, false) => (to_nbits <= 32 && cast_unsigned_to_signed, arch_32_suffix),
+ (false, true) => (
+ cast_unsigned_to_signed,
+ if from_nbits == 64 {
+ arch_64_suffix
+ } else {
+ arch_32_suffix
+ },
+ ),
+ };
+
+ if should_lint {
+ span_lint(
+ cx,
+ CAST_POSSIBLE_WRAP,
+ expr.span,
++ &format!("casting `{cast_from}` to `{cast_to}` may wrap around the value{suffix}",),
+ );
+ }
+}
--- /dev/null
- "casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
- cast_from,
- cast_to,
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_c_void;
+use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, match_any_def_paths, paths};
+use rustc_hir::{Expr, ExprKind, GenericArg};
+use rustc_lint::LateContext;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, Ty};
+
+use super::CAST_PTR_ALIGNMENT;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
+ if is_hir_ty_cfg_dependant(cx, cast_to) {
+ return;
+ }
+ let (cast_from, cast_to) = (
+ cx.typeck_results().expr_ty(cast_expr),
+ cx.typeck_results().expr_ty(expr),
+ );
+ lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
+ } else if let ExprKind::MethodCall(method_path, self_arg, ..) = &expr.kind {
+ if method_path.ident.name == sym!(cast)
+ && let Some(generic_args) = method_path.args
+ && let [GenericArg::Type(cast_to)] = generic_args.args
+ // There probably is no obvious reason to do this, just to be consistent with `as` cases.
+ && !is_hir_ty_cfg_dependant(cx, cast_to)
+ {
+ let (cast_from, cast_to) =
+ (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
+ lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
+ }
+ }
+}
+
+fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) {
+ if let ty::RawPtr(from_ptr_ty) = &cast_from.kind()
+ && let ty::RawPtr(to_ptr_ty) = &cast_to.kind()
+ && let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty)
+ && let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty)
+ && from_layout.align.abi < to_layout.align.abi
+ // with c_void, we inherently need to trust the user
+ && !is_c_void(cx, from_ptr_ty.ty)
+ // when casting from a ZST, we don't know enough to properly lint
+ && !from_layout.is_zst()
+ && !is_used_as_unaligned(cx, expr)
+ {
+ span_lint(
+ cx,
+ CAST_PTR_ALIGNMENT,
+ expr.span,
+ &format!(
++ "casting from `{cast_from}` to a more-strictly-aligned pointer (`{cast_to}`) ({} < {} bytes)",
+ from_layout.align.abi.bytes(),
+ to_layout.align.abi.bytes(),
+ ),
+ );
+ }
+}
+
+fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let Some(parent) = get_parent_expr(cx, e) else {
+ return false;
+ };
+ match parent.kind {
+ ExprKind::MethodCall(name, self_arg, ..) if self_arg.hir_id == e.hir_id => {
+ if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned")
+ && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
+ && let Some(def_id) = cx.tcx.impl_of_method(def_id)
+ && cx.tcx.type_of(def_id).is_unsafe_ptr()
+ {
+ true
+ } else {
+ false
+ }
+ },
+ ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => {
+ static PATHS: &[&[&str]] = &[
+ paths::PTR_READ_UNALIGNED.as_slice(),
+ paths::PTR_WRITE_UNALIGNED.as_slice(),
+ paths::PTR_UNALIGNED_VOLATILE_LOAD.as_slice(),
+ paths::PTR_UNALIGNED_VOLATILE_STORE.as_slice(),
+ ];
+ if let ExprKind::Path(path) = &func.kind
+ && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
+ && match_any_def_paths(cx, def_id, PATHS).is_some()
+ {
+ true
+ } else {
+ false
+ }
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!(
- "casting `{}` to `{}` may lose the sign of the value",
- cast_from, cast_to
- ),
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{method_chain_args, sext};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CAST_SIGN_LOSS;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ if should_lint(cx, cast_op, cast_from, cast_to) {
+ span_lint(
+ cx,
+ CAST_SIGN_LOSS,
+ expr.span,
++ &format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"),
+ );
+ }
+}
+
+fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool {
+ match (cast_from.is_integral(), cast_to.is_integral()) {
+ (true, true) => {
+ if !cast_from.is_signed() || cast_to.is_signed() {
+ return false;
+ }
+
+ // Don't lint for positive constants.
+ let const_val = constant(cx, cx.typeck_results(), cast_op);
+ if_chain! {
+ if let Some((Constant::Int(n), _)) = const_val;
+ if let ty::Int(ity) = *cast_from.kind();
+ if sext(cx.tcx, n, ity) >= 0;
+ then {
+ return false;
+ }
+ }
+
+ // Don't lint for the result of methods that always return non-negative values.
+ if let ExprKind::MethodCall(path, ..) = cast_op.kind {
+ let mut method_name = path.ident.name.as_str();
+ let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
+
+ if_chain! {
+ if method_name == "unwrap";
+ if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]);
+ if let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind;
+ then {
+ method_name = inner_path.ident.name.as_str();
+ }
+ }
+
+ if allowed_methods.iter().any(|&name| method_name == name) {
+ return false;
+ }
+ }
+
+ true
+ },
+
+ (false, true) => !cast_to.is_signed(),
+
+ (_, _) => false,
+ }
+}
--- /dev/null
- "casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
- start_ty.ty, from_size, end_ty.ty, to_size,
+use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source};
+use if_chain::if_chain;
+use rustc_ast::Mutability;
+use rustc_hir::{Expr, ExprKind, Node};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
+use rustc_semver::RustcVersion;
+
+use super::CAST_SLICE_DIFFERENT_SIZES;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Option<RustcVersion>) {
+ // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
+ if !meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS) {
+ return;
+ }
+
+ // if this cast is the child of another cast expression then don't emit something for it, the full
+ // chain will be analyzed
+ if is_child_of_cast(cx, expr) {
+ return;
+ }
+
+ if let Some(CastChainInfo {
+ left_cast,
+ start_ty,
+ end_ty,
+ }) = expr_cast_chain_tys(cx, expr)
+ {
+ if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
+ let from_size = from_layout.size.bytes();
+ let to_size = to_layout.size.bytes();
+ if from_size != to_size && from_size != 0 && to_size != 0 {
+ span_lint_and_then(
+ cx,
+ CAST_SLICE_DIFFERENT_SIZES,
+ expr.span,
+ &format!(
++ "casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
++ start_ty.ty, end_ty.ty,
+ ),
+ |diag| {
+ let ptr_snippet = source::snippet(cx, left_cast.span, "..");
+
+ let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
+ Mutability::Mut => ("_mut", "mut"),
+ Mutability::Not => ("", "const"),
+ };
+ let sugg = format!(
+ "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
+ // get just the ty from the TypeAndMut so that the printed type isn't something like `mut
+ // T`, extract just the `T`
+ end_ty.ty
+ );
+
+ diag.span_suggestion(
+ expr.span,
+ &format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
+ sugg,
+ rustc_errors::Applicability::HasPlaceholders,
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let map = cx.tcx.hir();
+ if_chain! {
+ if let Some(parent_id) = map.find_parent_node(expr.hir_id);
+ if let Some(parent) = map.find(parent_id);
+ then {
+ let expr = match parent {
+ Node::Block(block) => {
+ if let Some(parent_expr) = block.expr {
+ parent_expr
+ } else {
+ return false;
+ }
+ },
+ Node::Expr(expr) => expr,
+ _ => return false,
+ };
+
+ matches!(expr.kind, ExprKind::Cast(..))
+ } else {
+ false
+ }
+ }
+}
+
+/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
+/// the type is one of those slices
+fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
+ match ty.kind() {
+ ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
+ ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+struct CastChainInfo<'tcx> {
+ /// The left most part of the cast chain, or in other words, the first cast in the chain
+ /// Used for diagnostics
+ left_cast: &'tcx Expr<'tcx>,
+ /// The starting type of the cast chain
+ start_ty: TypeAndMut<'tcx>,
+ /// The final type of the cast chain
+ end_ty: TypeAndMut<'tcx>,
+}
+
+/// Returns a `CastChainInfo` with the left-most cast in the chain and the original ptr T and final
+/// ptr U if the expression is composed of casts.
+/// Returns None if the expr is not a Cast
+fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<CastChainInfo<'tcx>> {
+ if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
+ let cast_to = cx.typeck_results().expr_ty(expr);
+ let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
+
+ // If the expression that makes up the source of this cast is itself a cast, recursively
+ // call `expr_cast_chain_tys` and update the end type with the final target type.
+ // Otherwise, this cast is not immediately nested, just construct the info for this cast
+ if let Some(prev_info) = expr_cast_chain_tys(cx, cast_expr) {
+ Some(CastChainInfo {
+ end_ty: to_slice_ty,
+ ..prev_info
+ })
+ } else {
+ let cast_from = cx.typeck_results().expr_ty(cast_expr);
+ let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
+ Some(CastChainInfo {
+ left_cast: cast_expr,
+ start_ty: from_slice_ty,
+ end_ty: to_slice_ty,
+ })
+ }
+ } else {
+ None
+ }
+}
--- /dev/null
- format!("b{}", snippet),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, UintTy};
+
+use super::CHAR_LIT_AS_U8;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Cast(e, _) = &expr.kind;
+ if let ExprKind::Lit(l) = &e.kind;
+ if let LitKind::Char(c) = l.node;
+ if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
+
+ span_lint_and_then(
+ cx,
+ CHAR_LIT_AS_U8,
+ expr.span,
+ "casting a character literal to `u8` truncates",
+ |diag| {
+ diag.note("`char` is four bytes wide, but `u8` is a single byte");
+
+ if c.is_ascii() {
+ diag.span_suggestion(
+ expr.span,
+ "use a byte literal instead",
++ format!("b{snippet}"),
+ applicability,
+ );
+ }
+ });
+ }
+ }
+}
--- /dev/null
- &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty, UintTy};
+
+use super::{utils, FN_TO_NUMERIC_CAST};
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ // We only want to check casts to `ty::Uint` or `ty::Int`
+ match cast_to.kind() {
+ ty::Uint(_) | ty::Int(..) => { /* continue on */ },
+ _ => return,
+ }
+
+ match cast_from.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+
+ if (to_nbits >= cx.tcx.data_layout.pointer_size.bits()) && (*cast_to.kind() != ty::Uint(UintTy::Usize)) {
+ span_lint_and_sugg(
+ cx,
+ FN_TO_NUMERIC_CAST,
+ expr.span,
- format!("{} as usize", from_snippet),
++ &format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
+ "try",
++ format!("{from_snippet} as usize"),
+ applicability,
+ );
+ }
+ },
+ _ => {},
+ }
+}
--- /dev/null
- &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::FN_TO_NUMERIC_CAST_ANY;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ // We allow casts from any function type to any function type.
+ match cast_to.kind() {
+ ty::FnDef(..) | ty::FnPtr(..) => return,
+ _ => { /* continue to checks */ },
+ }
+
+ match cast_from.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
+
+ span_lint_and_sugg(
+ cx,
+ FN_TO_NUMERIC_CAST_ANY,
+ expr.span,
- format!("{}() as {}", from_snippet, cast_to),
++ &format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
+ "did you mean to invoke the function?",
++ format!("{from_snippet}() as {cast_to}"),
+ applicability,
+ );
+ },
+ _ => {},
+ }
+}
--- /dev/null
- &format!(
- "casting function pointer `{}` to `{}`, which truncates the value",
- from_snippet, cast_to
- ),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::{utils, FN_TO_NUMERIC_CAST_WITH_TRUNCATION};
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+ // We only want to check casts to `ty::Uint` or `ty::Int`
+ match cast_to.kind() {
+ ty::Uint(_) | ty::Int(..) => { /* continue on */ },
+ _ => return,
+ }
+ match cast_from.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
+
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+ if to_nbits < cx.tcx.data_layout.pointer_size.bits() {
+ span_lint_and_sugg(
+ cx,
+ FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ expr.span,
- format!("{} as usize", from_snippet),
++ &format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"),
+ "try",
++ format!("{from_snippet} as usize"),
+ applicability,
+ );
+ }
+ },
+ _ => {},
+ }
+}
--- /dev/null
- _ => Cow::Owned(format!("::<{}>", to_pointee_ty)),
+use std::borrow::Cow;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Mutability, TyKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, TypeAndMut};
+use rustc_semver::RustcVersion;
+
+use super::PTR_AS_PTR;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Option<RustcVersion>) {
+ if !meets_msrv(msrv, msrvs::POINTER_CAST) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind;
+ let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr));
+ if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind();
+ if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind();
+ if matches!((from_mutbl, to_mutbl),
+ (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut));
+ // The `U` in `pointer::cast` have to be `Sized`
+ // as explained here: https://github.com/rust-lang/rust/issues/60602.
+ if to_pointee_ty.is_sized(cx.tcx.at(expr.span), cx.param_env);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability);
+ let turbofish = match &cast_to_hir_ty.kind {
+ TyKind::Infer => Cow::Borrowed(""),
+ TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""),
- format!("{}.cast{}()", cast_expr_sugg.maybe_par(), turbofish),
++ _ => Cow::Owned(format!("::<{to_pointee_ty}>")),
+ };
+ span_lint_and_sugg(
+ cx,
+ PTR_AS_PTR,
+ expr.span,
+ "`as` casting between raw pointers without changing its mutability",
+ "try `pointer::cast`, a safer alternative",
++ format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()),
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- let literal_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
+use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::get_parent_expr;
+use clippy_utils::numeric_literal::NumericLiteral;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::{LitFloatType, LitIntType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, Lit, QPath, TyKind, UnOp};
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
+
+use super::UNNECESSARY_CAST;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &Expr<'tcx>,
+ cast_expr: &Expr<'tcx>,
+ cast_from: Ty<'tcx>,
+ cast_to: Ty<'tcx>,
+) -> bool {
+ // skip non-primitive type cast
+ if_chain! {
+ if let ExprKind::Cast(_, cast_to) = expr.kind;
+ if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
+ if let Res::PrimTy(_) = path.res;
+ then {}
+ else {
+ return false
+ }
+ }
+
++ let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
++
+ if let Some(lit) = get_numeric_literal(cast_expr) {
- lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to);
++ let literal_str = &cast_str;
+
+ if_chain! {
+ if let LitKind::Int(n, _) = lit.node;
+ if let Some(src) = snippet_opt(cx, cast_expr.span);
+ if cast_to.is_floating_point();
+ if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node);
+ let from_nbits = 128 - n.leading_zeros();
+ let to_nbits = fp_ty_mantissa_nbits(cast_to);
+ if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal();
+ then {
+ lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
+ return true
+ }
+ }
+
+ match lit.node {
+ LitKind::Int(_, LitIntType::Unsuffixed) if cast_to.is_integral() => {
- lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to);
++ lint_unnecessary_cast(cx, expr, literal_str, cast_from, cast_to);
++ return false;
+ },
+ LitKind::Float(_, LitFloatType::Unsuffixed) if cast_to.is_floating_point() => {
- LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {},
++ lint_unnecessary_cast(cx, expr, literal_str, cast_from, cast_to);
++ return false;
++ },
++ LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {
++ return false;
+ },
- _ => {
- if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) {
- span_lint_and_sugg(
- cx,
- UNNECESSARY_CAST,
- expr.span,
- &format!(
- "casting to the same type is unnecessary (`{}` -> `{}`)",
- cast_from, cast_to
- ),
- "try",
- literal_str,
- Applicability::MachineApplicable,
- );
- return true;
- }
- },
+ LitKind::Int(_, LitIntType::Signed(_) | LitIntType::Unsigned(_))
+ | LitKind::Float(_, LitFloatType::Suffixed(_))
+ if cast_from.kind() == cast_to.kind() =>
+ {
+ if let Some(src) = snippet_opt(cx, cast_expr.span) {
+ if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) {
+ lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
++ return true;
+ }
+ }
+ },
- fn lint_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) {
++ _ => {},
+ }
+ }
+
++ if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) {
++ span_lint_and_sugg(
++ cx,
++ UNNECESSARY_CAST,
++ expr.span,
++ &format!("casting to the same type is unnecessary (`{cast_from}` -> `{cast_to}`)"),
++ "try",
++ cast_str,
++ Applicability::MachineApplicable,
++ );
++ return true;
++ }
++
+ false
+}
+
- let replaced_literal;
- let matchless = if literal_str.contains(['(', ')']) {
- replaced_literal = literal_str.replace(['(', ')'], "");
- &replaced_literal
- } else {
- literal_str
++fn lint_unnecessary_cast(
++ cx: &LateContext<'_>,
++ expr: &Expr<'_>,
++ raw_literal_str: &str,
++ cast_from: Ty<'_>,
++ cast_to: Ty<'_>,
++) {
+ let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" };
- &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to),
++ // first we remove all matches so `-(1)` become `-1`, and remove trailing dots, so `1.` become `1`
++ let literal_str = raw_literal_str
++ .replace(['(', ')'], "")
++ .trim_end_matches('.')
++ .to_string();
++ // we know need to check if the parent is a method call, to add parenthesis accordingly (eg:
++ // (-1).foo() instead of -1.foo())
++ let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr)
++ && let ExprKind::MethodCall(..) = parent_expr.kind
++ && literal_str.starts_with('-')
++ {
++ format!("({literal_str}_{cast_to})")
++
++ } else {
++ format!("{literal_str}_{cast_to}")
+ };
++
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_CAST,
+ expr.span,
- format!("{}_{}", matchless.trim_end_matches('.'), cast_to),
++ &format!("casting {literal_kind_name} literal to `{cast_to}` is unnecessary"),
+ "try",
++ sugg,
+ Applicability::MachineApplicable,
+ );
+}
+
+fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> {
+ match expr.kind {
+ ExprKind::Lit(ref lit) => Some(lit),
+ ExprKind::Unary(UnOp::Neg, e) => {
+ if let ExprKind::Lit(ref lit) = e.kind {
+ Some(lit)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+/// Returns the mantissa bits wide of a fp type.
+/// Will return 0 if the type is not a fp
+fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 {
+ match typ.kind() {
+ ty::Float(FloatTy::F32) => 23,
+ ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52,
+ _ => 0,
+ }
+}
--- /dev/null
- use clippy_utils::{in_constant, meets_msrv, msrvs, SpanlessEq};
+//! lint on manually implemented checked conversions that could be transformed into `try_from`
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
- use rustc_ast::ast::LitKind;
++use clippy_utils::{in_constant, is_integer_literal, meets_msrv, msrvs, SpanlessEq};
+use if_chain::if_chain;
- format!("{}::try_from({}).is_ok()", to_type, snippet),
+use rustc_errors::Applicability;
+use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
+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 explicit bounds checking when casting.
+ ///
+ /// ### Why is this bad?
+ /// Reduces the readability of statements & is error prone.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo: u32 = 5;
+ /// foo <= i32::MAX as u32;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = 1;
+ /// # #[allow(unused)]
+ /// i32::try_from(foo).is_ok();
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub CHECKED_CONVERSIONS,
+ pedantic,
+ "`try_from` could replace manual bounds checking when casting"
+}
+
+pub struct CheckedConversions {
+ msrv: Option<RustcVersion>,
+}
+
+impl CheckedConversions {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
+ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
+ if !meets_msrv(self.msrv, msrvs::TRY_FROM) {
+ return;
+ }
+
+ let result = if_chain! {
+ if !in_constant(cx, item.hir_id);
+ if !in_external_macro(cx.sess(), item.span);
+ if let ExprKind::Binary(op, left, right) = &item.kind;
+
+ then {
+ match op.node {
+ BinOpKind::Ge | BinOpKind::Le => single_check(item),
+ BinOpKind::And => double_check(cx, left, right),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ };
+
+ if let Some(cv) = result {
+ if let Some(to_type) = cv.to_type {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ CHECKED_CONVERSIONS,
+ item.span,
+ "checked cast can be simplified",
+ "try",
- if_chain! {
- if let ExprKind::Lit(ref lit) = &check.kind;
- if let LitKind::Int(0, _) = &lit.node;
-
- then {
- Some(Conversion::new_any(candidate))
- } else {
- None
- }
- }
++ format!("{to_type}::try_from({snippet}).is_ok()"),
+ applicability,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Searches for a single check from unsigned to _ is done
+/// todo: check for case signed -> larger unsigned == only x >= 0
+fn single_check<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ check_upper_bound(expr).filter(|cv| cv.cvt == ConversionType::FromUnsigned)
+}
+
+/// Searches for a combination of upper & lower bound checks
+fn double_check<'a>(cx: &LateContext<'_>, left: &'a Expr<'_>, right: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ let upper_lower = |l, r| {
+ let upper = check_upper_bound(l);
+ let lower = check_lower_bound(r);
+
+ upper.zip(lower).and_then(|(l, r)| l.combine(r, cx))
+ };
+
+ upper_lower(left, right).or_else(|| upper_lower(right, left))
+}
+
+/// Contains the result of a tried conversion check
+#[derive(Clone, Debug)]
+struct Conversion<'a> {
+ cvt: ConversionType,
+ expr_to_cast: &'a Expr<'a>,
+ to_type: Option<&'a str>,
+}
+
+/// The kind of conversion that is checked
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum ConversionType {
+ SignedToUnsigned,
+ SignedToSigned,
+ FromUnsigned,
+}
+
+impl<'a> Conversion<'a> {
+ /// Combine multiple conversions if the are compatible
+ pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>> {
+ if self.is_compatible(&other, cx) {
+ // Prefer a Conversion that contains a type-constraint
+ Some(if self.to_type.is_some() { self } else { other })
+ } else {
+ None
+ }
+ }
+
+ /// Checks if two conversions are compatible
+ /// same type of conversion, same 'castee' and same 'to type'
+ pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>) -> bool {
+ (self.cvt == other.cvt)
+ && (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast))
+ && (self.has_compatible_to_type(other))
+ }
+
+ /// Checks if the to-type is the same (if there is a type constraint)
+ fn has_compatible_to_type(&self, other: &Self) -> bool {
+ match (self.to_type, other.to_type) {
+ (Some(l), Some(r)) => l == r,
+ _ => true,
+ }
+ }
+
+ /// Try to construct a new conversion if the conversion type is valid
+ fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option<Conversion<'a>> {
+ ConversionType::try_new(from_type, to_type).map(|cvt| Conversion {
+ cvt,
+ expr_to_cast,
+ to_type: Some(to_type),
+ })
+ }
+
+ /// Construct a new conversion without type constraint
+ fn new_any(expr_to_cast: &'a Expr<'_>) -> Conversion<'a> {
+ Conversion {
+ cvt: ConversionType::SignedToUnsigned,
+ expr_to_cast,
+ to_type: None,
+ }
+ }
+}
+
+impl ConversionType {
+ /// Creates a conversion type if the type is allowed & conversion is valid
+ #[must_use]
+ fn try_new(from: &str, to: &str) -> Option<Self> {
+ if UINTS.contains(&from) {
+ Some(Self::FromUnsigned)
+ } else if SINTS.contains(&from) {
+ if UINTS.contains(&to) {
+ Some(Self::SignedToUnsigned)
+ } else if SINTS.contains(&to) {
+ Some(Self::SignedToSigned)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr <= (to_type::MAX as from_type)`
+fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ if_chain! {
+ if let ExprKind::Binary(ref op, left, right) = &expr.kind;
+ if let Some((candidate, check)) = normalize_le_ge(op, left, right);
+ if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
+
+ then {
+ Conversion::try_new(candidate, from, to)
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr >= 0|(to_type::MIN as from_type)`
+fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option<Conversion<'a>> {
+ (check_lower_bound_zero(candidate, check)).or_else(|| (check_lower_bound_min(candidate, check)))
+ }
+
+ // First of we need a binary containing the expression & the cast
+ if let ExprKind::Binary(ref op, left, right) = &expr.kind {
+ normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
+ } else {
+ None
+ }
+}
+
+/// Check for `expr >= 0`
+fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> {
++ is_integer_literal(check, 0).then(|| Conversion::new_any(candidate))
+}
+
+/// Check for `expr >= (to_type::MIN as from_type)`
+fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") {
+ Conversion::try_new(candidate, from, to)
+ } else {
+ None
+ }
+}
+
+/// Tries to extract the from- and to-type from a cast expression
+fn get_types_from_cast<'a>(
+ expr: &'a Expr<'_>,
+ types: &'a [&str],
+ func: &'a str,
+ assoc_const: &'a str,
+) -> Option<(&'a str, &'a str)> {
+ // `to_type::max_value() as from_type`
+ // or `to_type::MAX as from_type`
+ let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
+ // to_type::max_value(), from_type
+ if let ExprKind::Cast(limit, from_type) = &expr.kind;
+ if let TyKind::Path(ref from_type_path) = &from_type.kind;
+ if let Some(from_sym) = int_ty_to_sym(from_type_path);
+
+ then {
+ Some((limit, from_sym))
+ } else {
+ None
+ }
+ };
+
+ // `from_type::from(to_type::max_value())`
+ let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
+ if_chain! {
+ // `from_type::from, to_type::max_value()`
+ if let ExprKind::Call(from_func, [limit]) = &expr.kind;
+ // `from_type::from`
+ if let ExprKind::Path(ref path) = &from_func.kind;
+ if let Some(from_sym) = get_implementing_type(path, INTS, "from");
+
+ then {
+ Some((limit, from_sym))
+ } else {
+ None
+ }
+ }
+ });
+
+ if let Some((limit, from_type)) = limit_from {
+ match limit.kind {
+ // `from_type::from(_)`
+ ExprKind::Call(path, _) => {
+ if let ExprKind::Path(ref path) = path.kind {
+ // `to_type`
+ if let Some(to_type) = get_implementing_type(path, types, func) {
+ return Some((from_type, to_type));
+ }
+ }
+ },
+ // `to_type::MAX`
+ ExprKind::Path(ref path) => {
+ if let Some(to_type) = get_implementing_type(path, types, assoc_const) {
+ return Some((from_type, to_type));
+ }
+ },
+ _ => {},
+ }
+ };
+ None
+}
+
+/// Gets the type which implements the called function
+fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
+ if_chain! {
+ if let QPath::TypeRelative(ty, path) = &path;
+ if path.ident.name.as_str() == function;
+ if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
+ if let [int] = tp.segments;
+ then {
+ let name = int.ident.name.as_str();
+ candidates.iter().find(|c| &name == *c).copied()
+ } else {
+ None
+ }
+ }
+}
+
+/// Gets the type as a string, if it is a supported integer
+fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
+ if_chain! {
+ if let QPath::Resolved(_, path) = *path;
+ if let [ty] = path.segments;
+ then {
+ let name = ty.ident.name.as_str();
+ INTS.iter().find(|c| &name == *c).copied()
+ } else {
+ None
+ }
+ }
+}
+
+/// Will return the expressions as if they were expr1 <= expr2
+fn normalize_le_ge<'a>(op: &BinOp, left: &'a Expr<'a>, right: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
+ match op.node {
+ BinOpKind::Le => Some((left, right)),
+ BinOpKind::Ge => Some((right, left)),
+ _ => None,
+ }
+}
+
+// Constants
+const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"];
+const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"];
+const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"];
--- /dev/null
- use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
- use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
+//! 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 clippy_utils::LimitStack;
++use core::ops::ControlFlow;
+use rustc_ast::ast::Attribute;
- let expr = &body.value;
++use rustc_hir::intravisit::FnKind;
++use rustc_hir::{Body, 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<'_>,
+ body: &'tcx Body<'_>,
+ body_span: Span,
+ ) {
+ if body_span.from_expansion() {
+ return;
+ }
+
- let mut helper = CcHelper { cc: 1, returns: 0 };
- helper.visit_expr(expr);
- let CcHelper { cc, returns } = helper;
++ let expr = body.value;
++
++ 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 mut rust_cc = cc;
+ 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)
+ };
+
- if rust_cc >= ret_adjust {
- rust_cc -= ret_adjust;
+ // prevent degenerate cases where unreachable code contains `return` statements
- if rust_cc > self.limit.limit() {
++ if cc >= ret_adjust {
++ cc -= ret_adjust;
+ }
+
- "the function has a cognitive complexity of ({}/{})",
- rust_cc,
++ 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!(
-
- struct CcHelper {
- cc: u64,
- returns: u64,
- }
-
- impl<'tcx> Visitor<'tcx> for CcHelper {
- fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
- walk_expr(self, e);
- match e.kind {
- ExprKind::If(_, _, _) => {
- self.cc += 1;
- },
- ExprKind::Match(_, arms, _) => {
- if arms.len() > 1 {
- self.cc += 1;
- }
- self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
- },
- ExprKind::Ret(_) => self.returns += 1,
- _ => {},
- }
- }
- }
++ "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) {
+ self.check(cx, kind, decl, body, 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
- &format!("calling `{}` is more clear than this expression", replacement),
+use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::ty::{has_drop, is_copy};
+use clippy_utils::{
+ any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro, match_def_path, paths,
+};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for literal calls to `Default::default()`.
+ ///
+ /// ### Why is this bad?
+ /// It's easier for the reader if the name of the type is used, rather than the
+ /// generic `Default`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s: String = Default::default();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let s = String::default();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DEFAULT_TRAIT_ACCESS,
+ pedantic,
+ "checks for literal calls to `Default::default()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for immediate reassignment of fields initialized
+ /// with Default::default().
+ ///
+ /// ### Why is this bad?
+ ///It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
+ ///
+ /// ### Known problems
+ /// Assignments to patterns that are of tuple type are not linted.
+ ///
+ /// ### Example
+ /// ```
+ /// # #[derive(Default)]
+ /// # struct A { i: i32 }
+ /// let mut a: A = Default::default();
+ /// a.i = 42;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```
+ /// # #[derive(Default)]
+ /// # struct A { i: i32 }
+ /// let a = A {
+ /// i: 42,
+ /// .. Default::default()
+ /// };
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub FIELD_REASSIGN_WITH_DEFAULT,
+ style,
+ "binding initialized with Default should have its fields set in the initializer"
+}
+
+#[derive(Default)]
+pub struct Default {
+ // Spans linted by `field_reassign_with_default`.
+ reassigned_linted: FxHashSet<Span>,
+}
+
+impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
+
+impl<'tcx> LateLintPass<'tcx> for Default {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ // Avoid cases already linted by `field_reassign_with_default`
+ if !self.reassigned_linted.contains(&expr.span);
+ if let ExprKind::Call(path, ..) = expr.kind;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
+ if !is_update_syntax_base(cx, expr);
+ // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
+ if let QPath::Resolved(None, _path) = qpath;
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(def, ..) = expr_ty.kind();
+ if !is_from_proc_macro(cx, expr);
+ then {
+ // TODO: Work out a way to put "whatever the imported way of referencing
+ // this type in this file" rather than a fully-qualified type.
+ let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did()));
+ span_lint_and_sugg(
+ cx,
+ DEFAULT_TRAIT_ACCESS,
+ expr.span,
- format!("{}: {}", field, value_snippet)
++ &format!("calling `{replacement}` is more clear than this expression"),
+ "try",
+ replacement,
+ Applicability::Unspecified, // First resolve the TODO above
+ );
+ }
+ }
+ }
+
+ #[expect(clippy::too_many_lines)]
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ // start from the `let mut _ = _::default();` and look at all the following
+ // statements, see if they re-assign the fields of the binding
+ let stmts_head = match block.stmts {
+ // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
+ [head @ .., _] if !head.is_empty() => head,
+ _ => return,
+ };
+ for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
+ // `default` method of the `Default` trait, and store statement index in current block being
+ // checked and the name of the bound variable
+ let (local, variant, binding_name, binding_type, span) = if_chain! {
+ // only take `let ...` statements
+ if let StmtKind::Local(local) = stmt.kind;
+ if let Some(expr) = local.init;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if !expr.span.from_expansion();
+ // only take bindings to identifiers
+ if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
+ // only when assigning `... = Default::default()`
+ if is_expr_default(expr, cx);
+ let binding_type = cx.typeck_results().node_type(binding_id);
+ if let Some(adt) = binding_type.ty_adt_def();
+ if adt.is_struct();
+ let variant = adt.non_enum_variant();
+ if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
+ let module_did = cx.tcx.parent_module(stmt.hir_id);
+ if variant
+ .fields
+ .iter()
+ .all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
+ let all_fields_are_copy = variant
+ .fields
+ .iter()
+ .all(|field| {
+ is_copy(cx, cx.tcx.type_of(field.did))
+ });
+ if !has_drop(cx, binding_type) || all_fields_are_copy;
+ then {
+ (local, variant, ident.name, binding_type, expr.span)
+ } else {
+ continue;
+ }
+ };
+
+ // find all "later statement"'s where the fields of the binding set as
+ // Default::default() get reassigned, unless the reassignment refers to the original binding
+ let mut first_assign = None;
+ let mut assigned_fields = Vec::new();
+ let mut cancel_lint = false;
+ for consecutive_statement in &block.stmts[stmt_idx + 1..] {
+ // find out if and which field was set by this `consecutive_statement`
+ if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
+ // interrupt and cancel lint if assign_rhs references the original binding
+ if contains_name(binding_name, assign_rhs) {
+ cancel_lint = true;
+ break;
+ }
+
+ // if the field was previously assigned, replace the assignment, otherwise insert the assignment
+ if let Some(prev) = assigned_fields
+ .iter_mut()
+ .find(|(field_name, _)| field_name == &field_ident.name)
+ {
+ *prev = (field_ident.name, assign_rhs);
+ } else {
+ assigned_fields.push((field_ident.name, assign_rhs));
+ }
+
+ // also set first instance of error for help message
+ if first_assign.is_none() {
+ first_assign = Some(consecutive_statement);
+ }
+ }
+ // interrupt if no field was assigned, since we only want to look at consecutive statements
+ else {
+ break;
+ }
+ }
+
+ // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
+ // construction using `Ty { fields, ..Default::default() }`
+ if !assigned_fields.is_empty() && !cancel_lint {
+ // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
+ let ext_with_default = !variant
+ .fields
+ .iter()
+ .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
+
+ let field_list = assigned_fields
+ .into_iter()
+ .map(|(field, rhs)| {
+ // extract and store the assigned value for help message
+ let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
- format!("{}::<{}>", adt_def_ty_name, &tys_str)
++ format!("{field}: {value_snippet}")
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
+
+ // give correct suggestion if generics are involved (see #6944)
+ let binding_type = if_chain! {
+ if let ty::Adt(adt_def, substs) = binding_type.kind();
+ if !substs.is_empty();
+ then {
+ let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
+ let generic_args = substs.iter().collect::<Vec<_>>();
+ let tys_str = generic_args
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>()
+ .join(", ");
- format!("{}::default()", binding_type)
++ format!("{adt_def_ty_name}::<{}>", &tys_str)
+ } else {
+ binding_type.to_string()
+ }
+ };
+
+ let sugg = if ext_with_default {
+ if field_list.is_empty() {
- format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
++ format!("{binding_type}::default()")
+ } else {
- format!("{} {{ {} }}", binding_type, field_list)
++ format!("{binding_type} {{ {field_list}, ..Default::default() }}")
+ }
+ } else {
- &format!(
- "consider initializing the variable with `{}` and removing relevant reassignments",
- sugg
- ),
++ format!("{binding_type} {{ {field_list} }}")
+ };
+
+ // span lint once per statement that binds default
+ span_lint_and_note(
+ cx,
+ FIELD_REASSIGN_WITH_DEFAULT,
+ first_assign.unwrap().span,
+ "field assignment outside of initializer for an instance created with Default::default()",
+ Some(local.span),
++ &format!("consider initializing the variable with `{sugg}` and removing relevant reassignments"),
+ );
+ self.reassigned_linted.insert(span);
+ }
+ }
+ }
+}
+
+/// Checks if the given expression is the `default` method belonging to the `Default` trait.
+fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, _) = &expr.kind;
+ if let ExprKind::Path(qpath) = &fn_expr.kind;
+ if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
+ then {
+ // right hand side of assignment is `Default::default`
+ match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD)
+ } else {
+ false
+ }
+ }
+}
+
+/// Returns the reassigned field and the assigning expression (right-hand side of assign).
+fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
+ if_chain! {
+ // only take assignments
+ if let StmtKind::Semi(later_expr) = this.kind;
+ if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
+ // only take assignments to fields where the left-hand side field is a field of
+ // the same binding as the previous statement
+ if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
+ if let Some(second_binding_name) = path.segments.last();
+ if second_binding_name.ident.name == binding_name;
+ then {
+ Some((field_ident, assign_rhs))
+ } else {
+ None
+ }
+ }
+}
+
+/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
+fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let ExprKind::Struct(_, _, Some(base)) = parent.kind;
+ then {
+ base.hir_id == expr.hir_id
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- #[clippy::version = "1.63.0"]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::last_path_segment;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{match_def_path, paths};
+use rustc_errors::Applicability;
+use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It checks for `std::iter::Empty::default()` and suggests replacing it with
+ /// `std::iter::empty()`.
+ /// ### Why is this bad?
+ /// `std::iter::empty()` is the more idiomatic way.
+ /// ### Example
+ /// ```rust
+ /// let _ = std::iter::Empty::<usize>::default();
+ /// let iter: std::iter::Empty<usize> = std::iter::Empty::default();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let _ = std::iter::empty::<usize>();
+ /// let iter: std::iter::Empty<usize> = std::iter::empty();
+ /// ```
++ #[clippy::version = "1.64.0"]
+ pub DEFAULT_INSTEAD_OF_ITER_EMPTY,
+ style,
+ "check `std::iter::Empty::default()` and replace with `std::iter::empty()`"
+}
+declare_lint_pass!(DefaultIterEmpty => [DEFAULT_INSTEAD_OF_ITER_EMPTY]);
+
+impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Call(iter_expr, []) = &expr.kind
+ && let ExprKind::Path(QPath::TypeRelative(ty, _)) = &iter_expr.kind
+ && let TyKind::Path(ty_path) = &ty.kind
+ && let QPath::Resolved(None, path) = ty_path
+ && let def::Res::Def(_, def_id) = &path.res
+ && match_def_path(cx, *def_id, &paths::ITER_EMPTY)
+ {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = make_sugg(cx, ty_path, &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ DEFAULT_INSTEAD_OF_ITER_EMPTY,
+ expr.span,
+ "`std::iter::empty()` is the more idiomatic way",
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+ }
+}
+
+fn make_sugg(cx: &LateContext<'_>, ty_path: &rustc_hir::QPath<'_>, applicability: &mut Applicability) -> String {
+ if let Some(last) = last_path_segment(ty_path).args
+ && let Some(iter_ty) = last.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ })
+ {
+ format!("std::iter::empty::<{}>()", snippet_with_applicability(cx, iter_ty.span, "..", applicability))
+ } else {
+ "std::iter::empty()".to_owned()
+ }
+}
--- /dev/null
- LitKind::Int(src, _) => format!("{}", src),
- LitKind::Float(src, _) => format!("{}", src),
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::numeric_literal;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::{
+ intravisit::{walk_expr, walk_stmt, Visitor},
+ Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::{
+ lint::in_external_macro,
+ ty::{self, FloatTy, IntTy, PolyFnSig, Ty},
+};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type
+ /// inference.
+ ///
+ /// Default numeric fallback means that if numeric types have not yet been bound to concrete
+ /// types at the end of type inference, then integer type is bound to `i32`, and similarly
+ /// floating type is bound to `f64`.
+ ///
+ /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback.
+ ///
+ /// ### Why is this bad?
+ /// For those who are very careful about types, default numeric fallback
+ /// can be a pitfall that cause unexpected runtime behavior.
+ ///
+ /// ### Known problems
+ /// This lint can only be allowed at the function level or above.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let i = 10;
+ /// let f = 1.23;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let i = 10i32;
+ /// let f = 1.23f64;
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub DEFAULT_NUMERIC_FALLBACK,
+ restriction,
+ "usage of unconstrained numeric literals which may cause default numeric fallback."
+}
+
+declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
+
+impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback {
+ fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ let mut visitor = NumericFallbackVisitor::new(cx);
+ visitor.visit_body(body);
+ }
+}
+
+struct NumericFallbackVisitor<'a, 'tcx> {
+ /// Stack manages type bound of exprs. The top element holds current expr type.
+ ty_bounds: Vec<TyBound<'tcx>>,
+
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ ty_bounds: vec![TyBound::Nothing],
+ cx,
+ }
+ }
+
+ /// Check whether a passed literal has potential to cause fallback or not.
+ fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) {
+ if_chain! {
+ if !in_external_macro(self.cx.sess(), lit.span);
+ if let Some(ty_bound) = self.ty_bounds.last();
+ if matches!(lit.node,
+ LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
+ if !ty_bound.is_numeric();
+ then {
+ let (suffix, is_float) = match lit_ty.kind() {
+ ty::Int(IntTy::I32) => ("i32", false),
+ ty::Float(FloatTy::F64) => ("f64", true),
+ // Default numeric fallback never results in other types.
+ _ => return,
+ };
+
+ let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
+ src
+ } else {
+ match lit.node {
++ LitKind::Int(src, _) => format!("{src}"),
++ LitKind::Float(src, _) => format!("{src}"),
+ _ => return,
+ }
+ };
+ let sugg = numeric_literal::format(&src, Some(suffix), is_float);
+ span_lint_hir_and_then(
+ self.cx,
+ DEFAULT_NUMERIC_FALLBACK,
+ emit_hir_id,
+ lit.span,
+ "default numeric fallback might occur",
+ |diag| {
+ diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
+ }
+ );
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ match &expr.kind {
+ ExprKind::Call(func, args) => {
+ if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
+ for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) {
+ // Push found arg type, then visit arg.
+ self.ty_bounds.push(TyBound::Ty(*bound));
+ self.visit_expr(expr);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ },
+
+ ExprKind::MethodCall(_, receiver, args, _) => {
+ if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+ let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder();
+ for (expr, bound) in iter::zip(std::iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) {
+ self.ty_bounds.push(TyBound::Ty(*bound));
+ self.visit_expr(expr);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ },
+
+ ExprKind::Struct(_, fields, base) => {
+ let ty = self.cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ if let Some(adt_def) = ty.ty_adt_def();
+ if adt_def.is_struct();
+ if let Some(variant) = adt_def.variants().iter().next();
+ then {
+ let fields_def = &variant.fields;
+
+ // Push field type then visit each field expr.
+ for field in fields.iter() {
+ let bound =
+ fields_def
+ .iter()
+ .find_map(|f_def| {
+ if f_def.ident(self.cx.tcx) == field.ident
+ { Some(self.cx.tcx.type_of(f_def.did)) }
+ else { None }
+ });
+ self.ty_bounds.push(bound.into());
+ self.visit_expr(field.expr);
+ self.ty_bounds.pop();
+ }
+
+ // Visit base with no bound.
+ if let Some(base) = base {
+ self.ty_bounds.push(TyBound::Nothing);
+ self.visit_expr(base);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ }
+ },
+
+ ExprKind::Lit(lit) => {
+ let ty = self.cx.typeck_results().expr_ty(expr);
+ self.check_lit(lit, ty, expr.hir_id);
+ return;
+ },
+
+ _ => {},
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if local.ty.is_some() {
+ self.ty_bounds.push(TyBound::Any);
+ } else {
+ self.ty_bounds.push(TyBound::Nothing);
+ }
+ },
+
+ _ => self.ty_bounds.push(TyBound::Nothing),
+ }
+
+ walk_stmt(self, stmt);
+ self.ty_bounds.pop();
+ }
+}
+
+fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> {
+ let node_ty = cx.typeck_results().node_type_opt(hir_id)?;
+ // We can't use `Ty::fn_sig` because it automatically performs substs, this may result in FNs.
+ match node_ty.kind() {
+ ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)),
+ ty::FnPtr(fn_sig) => Some(*fn_sig),
+ _ => None,
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum TyBound<'tcx> {
+ Any,
+ Ty(Ty<'tcx>),
+ Nothing,
+}
+
+impl<'tcx> TyBound<'tcx> {
+ fn is_numeric(self) -> bool {
+ match self {
+ TyBound::Any => true,
+ TyBound::Ty(t) => t.is_numeric(),
+ TyBound::Nothing => false,
+ }
+ }
+}
+
+impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> {
+ fn from(v: Option<Ty<'tcx>>) -> Self {
+ match v {
+ Some(t) => TyBound::Ty(t),
+ None => TyBound::Nothing,
+ }
+ }
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{self as hir, HirId, Item, ItemKind};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Displays a warning when a union is declared with the default representation (without a `#[repr(C)]` attribute).
+ ///
+ /// ### Why is this bad?
+ /// Unions in Rust have unspecified layout by default, despite many people thinking that they
+ /// lay out each field at the start of the union (like C does). That is, there are no guarantees
+ /// about the offset of the fields for unions with multiple non-ZST fields without an explicitly
+ /// specified layout. These cases may lead to undefined behavior in unsafe blocks.
+ ///
+ /// ### Example
+ /// ```rust
+ /// union Foo {
+ /// a: i32,
+ /// b: u32,
+ /// }
+ ///
+ /// fn main() {
+ /// let _x: u32 = unsafe {
+ /// Foo { a: 0_i32 }.b // Undefined behavior: `b` is allowed to be padding
+ /// };
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[repr(C)]
+ /// union Foo {
+ /// a: i32,
+ /// b: u32,
+ /// }
+ ///
+ /// fn main() {
+ /// let _x: u32 = unsafe {
+ /// Foo { a: 0_i32 }.b // Now defined behavior, this is just an i32 -> u32 transmute
+ /// };
+ /// }
+ /// ```
+ #[clippy::version = "1.60.0"]
+ pub DEFAULT_UNION_REPRESENTATION,
+ restriction,
+ "unions without a `#[repr(C)]` attribute"
+}
+declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION]);
+
+impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) {
+ span_lint_and_help(
+ cx,
+ DEFAULT_UNION_REPRESENTATION,
+ item.span,
+ "this union has the default representation",
+ None,
+ &format!(
+ "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout",
+ cx.tcx.def_path_str(item.def_id.to_def_id())
+ ),
+ );
+ }
+ }
+}
+
+/// Returns true if the given item is a union with at least two non-ZST fields.
+fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Union(data, _) = &item.kind {
+ data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2
+ } else {
+ false
+ }
+}
+
+fn is_zst(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) -> bool {
+ if hir_ty.span.from_expansion() {
+ return false;
+ }
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let Ok(layout) = cx.layout_of(ty) {
+ layout.is_zst()
+ } else {
+ false
+ }
+}
+
+fn has_c_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ cx.tcx.hir().attrs(hir_id).iter().any(|attr| {
+ if attr.has_name(sym::repr) {
+ if let Some(items) = attr.meta_item_list() {
+ for item in items {
+ if item.is_word() && matches!(item.name_or_empty(), sym::C) {
+ return true;
+ }
+ }
+ }
+ }
+ false
+ })
+}
--- /dev/null
- #[clippy::version = "1.60.0"]
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::sugg::has_enclosing_paren;
+use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res};
+use clippy_utils::{
+ fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, meets_msrv, msrvs, path_to_local,
+ walk_to_expr_usage,
+};
+use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_ty, Visitor};
+use rustc_hir::{
+ self as hir, def_id::DefId, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy,
+ GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind,
+ Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp,
+};
+use rustc_index::bit_set::BitSet;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
+use rustc_middle::ty::{
+ self, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind,
+ ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults,
+};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP};
+use rustc_trait_selection::infer::InferCtxtExt 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 {
+ 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>>,
+
+ // `IntoIterator` for arrays requires Rust 1.53.
+ msrv: Option<RustcVersion>,
+}
+
+impl Dereferencing {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Dereferencing::default()
+ }
+ }
+}
+
++#[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 {
+ #[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();
+ let (kind, sub_expr) = if let Some(x) = try_parse_ref_op(cx.tcx, typeck, expr) {
+ x
+ } 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, expr, self.msrv);
- if let Position::FieldAccess(name) = position
+ match kind {
+ RefOp::Deref => {
- if let Position::FieldAccess(name) = position
++ 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);
- #[derive(Clone, Copy)]
++ 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 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.
- FieldAccess(Symbol),
++#[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),
- matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee)
++ 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 {
- | Self::FieldAccess(_)
++ 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
- ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)),
++ | 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>,
+ 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(..),
+ def_id,
+ span,
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ def_id,
+ span,
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ def_id,
+ span,
+ ..
+ }) if span.ctxt() == ctxt => {
+ let ty = cx.tcx.type_of(def_id.def_id);
+ Some(ty_auto_deref_stability(cx, ty, precedence).position_for_result(cx))
+ },
+
+ Node::Item(&Item {
+ kind: ItemKind::Fn(..),
+ def_id,
+ span,
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Fn(..),
+ def_id,
+ span,
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(..),
+ def_id,
+ span,
+ ..
+ }) if span.ctxt() == ctxt => {
+ let output = cx
+ .tcx
+ .erase_late_bound_regions(cx.tcx.fn_sig(def_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)| {
+ sig.input_with_hir(i).map(|(hir_ty, ty)| match hir_ty {
+ // Type inference for closures can depend on how they're called. Only go by the explicit
+ // types here.
+ Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()),
+ None => {
+ if let ty::Param(param_ty) = ty.skip_binder().kind() {
+ needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv)
+ } else {
+ ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
+ .position_for_arg()
+ }
+ },
+ })
+ }),
+ ExprKind::MethodCall(_, 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()
+ && cx.tcx.infer_ctxt().enter(|infcx|
+ 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];
+ if let ty::Param(param_ty) = ty.kind() {
+ needless_borrow_impl_arg_position(cx, 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()
+ }
+ })
+ },
- format!("({})", expr_str)
++ 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.at(DUMMY_SP), 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.at(DUMMY_SP), 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
+}
+
+// Checks whether:
+// * child is an expression of the form `&e` in an argument position requiring an `impl Trait`
+// * `e`'s type implements `Trait` and is copyable
+// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
+// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
+// be moved, but it cannot be.
+fn needless_borrow_impl_arg_position<'tcx>(
+ cx: &LateContext<'tcx>,
+ parent: &Expr<'tcx>,
+ arg_index: usize,
+ param_ty: ParamTy,
+ mut expr: &Expr<'tcx>,
+ precedence: i8,
+ msrv: Option<RustcVersion>,
+) -> Position {
+ let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
+ let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
+
+ let Some(callee_def_id) = fn_def_id(cx, parent) else { return Position::Other(precedence) };
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ let substs_with_expr_ty = cx
+ .typeck_results()
+ .node_substs(if let ExprKind::Call(callee, _) = parent.kind {
+ callee.hir_id
+ } else {
+ parent.hir_id
+ });
+
+ let predicates = cx.tcx.param_env(callee_def_id).caller_bounds();
+ let projection_predicates = predicates
+ .iter()
+ .filter_map(|predicate| {
+ if let PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ Some(projection_predicate)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let mut trait_with_ref_mut_self_method = false;
+
+ // If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return.
+ if predicates
+ .iter()
+ .filter_map(|predicate| {
+ if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
+ {
+ Some(trait_predicate.trait_ref.def_id)
+ } else {
+ None
+ }
+ })
+ .inspect(|trait_def_id| {
+ trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id);
+ })
+ .all(|trait_def_id| {
+ Some(trait_def_id) == destruct_trait_def_id
+ || Some(trait_def_id) == sized_trait_def_id
+ || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
+ })
+ {
+ return Position::Other(precedence);
+ }
+
+ // `substs_with_referent_ty` can be constructed outside of `check_referent` because the same
+ // elements are modified each time `check_referent` is called.
+ let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
+
+ let mut check_referent = |referent| {
+ let referent_ty = cx.typeck_results().expr_ty(referent);
+
+ if !is_copy(cx, referent_ty) {
+ return false;
+ }
+
+ // https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
+ if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
+ return false;
+ }
+
+ if !replace_types(
+ cx,
+ param_ty,
+ referent_ty,
+ fn_sig,
+ arg_index,
+ &projection_predicates,
+ &mut substs_with_referent_ty,
+ ) {
+ return false;
+ }
+
+ predicates.iter().all(|predicate| {
+ if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id)
+ && let ty::Param(param_ty) = trait_predicate.self_ty().kind()
+ && let GenericArgKind::Type(ty) = substs_with_referent_ty[param_ty.index as usize].unpack()
+ && ty.is_array()
+ && !meets_msrv(msrv, msrvs::ARRAY_INTO_ITERATOR)
+ {
+ return false;
+ }
+
+ let predicate = EarlyBinder(predicate).subst(cx.tcx, &substs_with_referent_ty);
+ let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate);
+ cx.tcx
+ .infer_ctxt()
+ .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation))
+ })
+ };
+
+ let mut needless_borrow = false;
+ while let ExprKind::AddrOf(_, _, referent) = expr.kind {
+ if !check_referent(referent) {
+ break;
+ }
+ expr = referent;
+ needless_borrow = true;
+ }
+
+ if needless_borrow {
+ Position::ImplArg(expr.hir_id)
+ } else {
+ Position::Other(precedence)
+ }
+}
+
+fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
+ cx.tcx
+ .associated_items(trait_def_id)
+ .in_definition_order()
+ .any(|assoc_item| {
+ if assoc_item.fn_has_self_parameter {
+ let self_ty = cx.tcx.fn_sig(assoc_item.def_id).skip_binder().inputs()[0];
+ matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut))
+ } else {
+ false
+ }
+ })
+}
+
+// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
+// projected type that is a type parameter. Returns `false` if replacing the types would have an
+// effect on the function signature beyond substituting `new_ty` for `param_ty`.
+// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
+fn replace_types<'tcx>(
+ cx: &LateContext<'tcx>,
+ param_ty: ParamTy,
+ new_ty: Ty<'tcx>,
+ fn_sig: FnSig<'tcx>,
+ arg_index: usize,
+ projection_predicates: &[ProjectionPredicate<'tcx>],
+ substs: &mut [ty::GenericArg<'tcx>],
+) -> bool {
+ let mut replaced = BitSet::new_empty(substs.len());
+
+ let mut deque = VecDeque::with_capacity(substs.len());
+ deque.push_back((param_ty, new_ty));
+
+ while let Some((param_ty, new_ty)) = deque.pop_front() {
+ // If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
+ if !fn_sig
+ .inputs_and_output
+ .iter()
+ .enumerate()
+ .all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx)))
+ {
+ return false;
+ }
+
+ substs[param_ty.index as usize] = ty::GenericArg::from(new_ty);
+
+ // The `replaced.insert(...)` check provides some protection against infinite loops.
+ if replaced.insert(param_ty.index) {
+ for projection_predicate in projection_predicates {
+ if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
+ && let 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.at(DUMMY_SP), 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::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.at(DUMMY_SP), 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!("{}{}{}", addr_of_str, deref_str, expr_str),
++ 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!("({})", snip)
++ 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!("{}({})", prefix, snip)
++ 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)
++ format!("{prefix}({snip})")
+ } else {
- pat.replacements.push((e.span, format!("&{}", snip)));
++ 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 Dereferencing {
+ fn check_local_usage<'tcx>(&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)));
++ 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
- style,
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::paths;
+use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
+use clippy_utils::{is_lint_allowed, match_def_path};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
+use rustc_hir::{
+ self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource,
+ Unsafety,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::traits::Reveal;
+use rustc_middle::ty::{
+ self, Binder, BoundConstness, GenericParamDefKind, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef,
+ Ty, TyCtxt,
+};
+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 deriving `Hash` but implementing `PartialEq`
+ /// explicitly or vice versa.
+ ///
+ /// ### Why is this bad?
+ /// The implementation of these traits must agree (for
+ /// example for use with `HashMap`) so it’s probably a bad idea to use a
+ /// default-generated `Hash` implementation with an explicitly defined
+ /// `PartialEq`. In particular, the following must hold for any type:
+ ///
+ /// ```text
+ /// k1 == k2 ⇒ hash(k1) == hash(k2)
+ /// ```
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[derive(Hash)]
+ /// struct Foo;
+ ///
+ /// impl PartialEq for Foo {
+ /// ...
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DERIVE_HASH_XOR_EQ,
+ correctness,
+ "deriving `Hash` but implementing `PartialEq` explicitly"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `Ord` but implementing `PartialOrd`
+ /// explicitly or vice versa.
+ ///
+ /// ### Why is this bad?
+ /// The implementation of these traits must agree (for
+ /// example for use with `sort`) so it’s probably a bad idea to use a
+ /// default-generated `Ord` implementation with an explicitly defined
+ /// `PartialOrd`. In particular, the following must hold for any type
+ /// implementing `Ord`:
+ ///
+ /// ```text
+ /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
+ /// ```
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Ord, PartialEq, Eq)]
+ /// struct Foo;
+ ///
+ /// impl PartialOrd for Foo {
+ /// ...
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// #[derive(PartialEq, Eq)]
+ /// struct Foo;
+ ///
+ /// impl PartialOrd for Foo {
+ /// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
+ /// Some(self.cmp(other))
+ /// }
+ /// }
+ ///
+ /// impl Ord for Foo {
+ /// ...
+ /// }
+ /// ```
+ /// or, if you don't need a custom ordering:
+ /// ```rust,ignore
+ /// #[derive(Ord, PartialOrd, PartialEq, Eq)]
+ /// struct Foo;
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub DERIVE_ORD_XOR_PARTIAL_ORD,
+ correctness,
+ "deriving `Ord` but implementing `PartialOrd` explicitly"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit `Clone` implementations for `Copy`
+ /// types.
+ ///
+ /// ### Why is this bad?
+ /// To avoid surprising behavior, these traits should
+ /// agree and the behavior of `Copy` cannot be overridden. In almost all
+ /// situations a `Copy` type should have a `Clone` implementation that does
+ /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
+ /// gets you.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Copy)]
+ /// struct Foo;
+ ///
+ /// impl Clone for Foo {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPL_IMPL_CLONE_ON_COPY,
+ pedantic,
+ "implementing `Clone` explicitly on `Copy` types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `serde::Deserialize` on a type that
+ /// has methods using `unsafe`.
+ ///
+ /// ### Why is this bad?
+ /// Deriving `serde::Deserialize` will create a constructor
+ /// that may violate invariants hold by another constructor.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use serde::Deserialize;
+ ///
+ /// #[derive(Deserialize)]
+ /// pub struct Foo {
+ /// // ..
+ /// }
+ ///
+ /// impl Foo {
+ /// pub fn new() -> Self {
+ /// // setup here ..
+ /// }
+ ///
+ /// pub unsafe fn parts() -> (&str, &str) {
+ /// // assumes invariants hold
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub UNSAFE_DERIVE_DESERIALIZE,
+ pedantic,
+ "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for types that derive `PartialEq` and could implement `Eq`.
+ ///
+ /// ### Why is this bad?
+ /// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
+ /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
+ /// in APIs that require `Eq` types. It also allows structs containing `T` to derive
+ /// `Eq` themselves.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[derive(PartialEq)]
+ /// struct Foo {
+ /// i_am_eq: i32,
+ /// i_am_eq_too: Vec<String>,
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[derive(PartialEq, Eq)]
+ /// struct Foo {
+ /// i_am_eq: i32,
+ /// i_am_eq_too: Vec<String>,
+ /// }
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
++ nursery,
+ "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
+}
+
+declare_lint_pass!(Derive => [
+ EXPL_IMPL_CLONE_ON_COPY,
+ DERIVE_HASH_XOR_EQ,
+ DERIVE_ORD_XOR_PARTIAL_ORD,
+ UNSAFE_DERIVE_DESERIALIZE,
+ DERIVE_PARTIAL_EQ_WITHOUT_EQ
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Derive {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Impl(Impl {
+ of_trait: Some(ref trait_ref),
+ ..
+ }) = item.kind
+ {
+ let ty = cx.tcx.type_of(item.def_id);
+ let is_automatically_derived = cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived);
+
+ check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
+ check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
+
+ if is_automatically_derived {
+ check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
+ check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
+ } else {
+ check_copy_clone(cx, item, trait_ref, ty);
+ }
+ }
+ }
+}
+
+/// Implementation of the `DERIVE_HASH_XOR_EQ` lint.
+fn check_hash_peq<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ trait_ref: &hir::TraitRef<'_>,
+ ty: Ty<'tcx>,
+ hash_is_automatically_derived: bool,
+) {
+ if_chain! {
+ if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
+ if let Some(def_id) = trait_ref.trait_def_id();
+ if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
+ then {
+ // Look for the PartialEq implementations for `ty`
+ cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
+ let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
+
+ if peq_is_automatically_derived == hash_is_automatically_derived {
+ return;
+ }
+
+ let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
+
+ // Only care about `impl PartialEq<Foo> for Foo`
+ // For `impl PartialEq<B> for A, input_types is [A, B]
+ if trait_ref.substs.type_at(1) == ty {
+ let mess = if peq_is_automatically_derived {
+ "you are implementing `Hash` explicitly but have derived `PartialEq`"
+ } else {
+ "you are deriving `Hash` but have implemented `PartialEq` explicitly"
+ };
+
+ span_lint_and_then(
+ cx,
+ DERIVE_HASH_XOR_EQ,
+ span,
+ mess,
+ |diag| {
+ if let Some(local_def_id) = impl_id.as_local() {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ diag.span_note(
+ cx.tcx.hir().span(hir_id),
+ "`PartialEq` implemented here"
+ );
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+}
+
+/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
+fn check_ord_partial_ord<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ trait_ref: &hir::TraitRef<'_>,
+ ty: Ty<'tcx>,
+ ord_is_automatically_derived: bool,
+) {
+ if_chain! {
+ if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
+ if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
+ if let Some(def_id) = &trait_ref.trait_def_id();
+ if *def_id == ord_trait_def_id;
+ then {
+ // Look for the PartialOrd implementations for `ty`
+ cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
+ let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
+
+ if partial_ord_is_automatically_derived == ord_is_automatically_derived {
+ return;
+ }
+
+ let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
+
+ // Only care about `impl PartialOrd<Foo> for Foo`
+ // For `impl PartialOrd<B> for A, input_types is [A, B]
+ if trait_ref.substs.type_at(1) == ty {
+ let mess = if partial_ord_is_automatically_derived {
+ "you are implementing `Ord` explicitly but have derived `PartialOrd`"
+ } else {
+ "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
+ };
+
+ span_lint_and_then(
+ cx,
+ DERIVE_ORD_XOR_PARTIAL_ORD,
+ span,
+ mess,
+ |diag| {
+ if let Some(local_def_id) = impl_id.as_local() {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ diag.span_note(
+ cx.tcx.hir().span(hir_id),
+ "`PartialOrd` implemented here"
+ );
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+}
+
+/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
+fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
+ let clone_id = match cx.tcx.lang_items().clone_trait() {
+ Some(id) if trait_ref.trait_def_id() == Some(id) => id,
+ _ => return,
+ };
+ let copy_id = match cx.tcx.lang_items().copy_trait() {
+ Some(id) => id,
+ None => return,
+ };
+ let (ty_adt, ty_subs) = match *ty.kind() {
+ // Unions can't derive clone.
+ ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
+ _ => return,
+ };
+ // If the current self type doesn't implement Copy (due to generic constraints), search to see if
+ // there's a Copy impl for any instance of the adt.
+ if !is_copy(cx, ty) {
+ if ty_subs.non_erasable_generics().next().is_some() {
+ let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| {
+ impls
+ .iter()
+ .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
+ });
+ if !has_copy_impl {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+ // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
+ // this impl.
+ if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
+ return;
+ }
+
+ span_lint_and_note(
+ cx,
+ EXPL_IMPL_CLONE_ON_COPY,
+ item.span,
+ "you are implementing `Clone` explicitly on a `Copy` type",
+ Some(item.span),
+ "consider deriving `Clone` or removing `Copy`",
+ );
+}
+
+/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
+fn check_unsafe_derive_deserialize<'tcx>(
+ cx: &LateContext<'tcx>,
+ item: &Item<'_>,
+ trait_ref: &hir::TraitRef<'_>,
+ ty: Ty<'tcx>,
+) {
+ fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
+ let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
+ walk_item(&mut visitor, item);
+ visitor.has_unsafe
+ }
+
+ if_chain! {
+ if let Some(trait_def_id) = trait_ref.trait_def_id();
+ if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
+ if let ty::Adt(def, _) = ty.kind();
+ if let Some(local_def_id) = def.did().as_local();
+ let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
+ if cx.tcx.inherent_impls(def.did())
+ .iter()
+ .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
+ .any(|imp| has_unsafe(cx, imp));
+ then {
+ span_lint_and_help(
+ cx,
+ UNSAFE_DERIVE_DESERIALIZE,
+ item.span,
+ "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
+ None,
+ "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
+ );
+ }
+ }
+}
+
+struct UnsafeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ has_unsafe: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: HirId) {
+ if self.has_unsafe {
+ return;
+ }
+
+ if_chain! {
+ if let Some(header) = kind.header();
+ if header.unsafety == Unsafety::Unsafe;
+ then {
+ self.has_unsafe = true;
+ }
+ }
+
+ walk_fn(self, kind, decl, body_id, id);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.has_unsafe {
+ return;
+ }
+
+ if let ExprKind::Block(block, _) = expr.kind {
+ if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
+ self.has_unsafe = true;
+ }
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
+fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
+ if_chain! {
+ if let ty::Adt(adt, substs) = ty.kind();
+ if cx.tcx.visibility(adt.did()).is_public();
+ if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
+ if let Some(def_id) = trait_ref.trait_def_id();
+ if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
+ let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id);
+ if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]);
+ // If all of our fields implement `Eq`, we can implement `Eq` too
+ if adt
+ .all_fields()
+ .map(|f| f.ty(cx.tcx, substs))
+ .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]));
+ then {
+ span_lint_and_sugg(
+ cx,
+ DERIVE_PARTIAL_EQ_WITHOUT_EQ,
+ span.ctxt().outer_expn_data().call_site,
+ "you are deriving `PartialEq` and can implement `Eq`",
+ "consider deriving `Eq` as well",
+ "PartialEq, Eq".to_string(),
+ Applicability::MachineApplicable,
+ )
+ }
+ }
+}
+
+/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
+fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ParamEnv<'_> {
+ // Initial map from generic index to param def.
+ // Vec<(param_def, needs_eq)>
+ let mut params = tcx
+ .generics_of(did)
+ .params
+ .iter()
+ .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. })))
+ .collect::<Vec<_>>();
+
+ let ty_predicates = tcx.predicates_of(did).predicates;
+ for (p, _) in ty_predicates {
+ if let PredicateKind::Trait(p) = p.kind().skip_binder()
+ && p.trait_ref.def_id == eq_trait_id
+ && let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
+ && p.constness == BoundConstness::NotConst
+ {
+ // Flag types which already have an `Eq` bound.
+ params[self_ty.index as usize].1 = false;
+ }
+ }
+
+ ParamEnv::new(
+ tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain(
+ params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| {
+ tcx.mk_predicate(Binder::dummy(PredicateKind::Trait(TraitPredicate {
+ trait_ref: TraitRef::new(
+ eq_trait_id,
+ tcx.mk_substs(std::iter::once(tcx.mk_param_from_def(param))),
+ ),
+ constness: BoundConstness::NotConst,
+ polarity: ImplPolarity::Positive,
+ })))
+ }),
+ )),
+ Reveal::UserFacing,
+ Constness::NotConst,
+ )
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::macros::macro_backtrace;
++use rustc_data_structures::fx::FxHashSet;
++use rustc_hir::def::{Namespace, Res};
++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() {
++ diag.note(&format!("{reason} (from clippy.toml)"));
++ }
++ },
++ );
++ }
++ }
++ }
++}
++
++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();
++ if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::MacroNS)) {
++ 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::Res, def_id::DefIdMap, Expr, ExprKind};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{fn_def_id, get_parent_expr, path_def_id};
+
- conf_disallowed: Vec<conf::DisallowedMethod>,
++use rustc_hir::def::{Namespace, Res};
++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 {
- pub fn new(conf_disallowed: Vec<conf::DisallowedMethod>) -> Self {
++ conf_disallowed: Vec<conf::DisallowedPath>,
+ disallowed: DefIdMap<usize>,
+}
+
+impl DisallowedMethods {
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
++ 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();
- if let conf::DisallowedMethod::WithReason {
- reason: Some(reason), ..
- } = conf
- {
- diag.note(&format!("{} (from clippy.toml)", reason));
++ if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::ValueNS)) {
+ 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 def_id = match uncalled_path.or_else(|| fn_def_id(cx, expr)) {
+ Some(def_id) => def_id,
+ None => 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(&format!("{reason} (from clippy.toml)"));
+ }
+ });
+ }
+}
--- /dev/null
- "identifier `{}` has a Unicode script that is not allowed by configuration: {}",
- symbol_str,
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use unicode_script::{Script, UnicodeScript};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of unicode scripts other than those explicitly allowed
+ /// by the lint config.
+ ///
+ /// This lint doesn't take into account non-text scripts such as `Unknown` and `Linear_A`.
+ /// It also ignores the `Common` script type.
+ /// While configuring, be sure to use official script name [aliases] from
+ /// [the list of supported scripts][supported_scripts].
+ ///
+ /// See also: [`non_ascii_idents`].
+ ///
+ /// [aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases
+ /// [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html
+ ///
+ /// ### Why is this bad?
+ /// It may be not desired to have many different scripts for
+ /// identifiers in the codebase.
+ ///
+ /// Note that if you only want to allow plain English, you might want to use
+ /// built-in [`non_ascii_idents`] lint instead.
+ ///
+ /// [`non_ascii_idents`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Assuming that `clippy.toml` contains the following line:
+ /// // allowed-locales = ["Latin", "Cyrillic"]
+ /// let counter = 10; // OK, latin is allowed.
+ /// let счётчик = 10; // OK, cyrillic is allowed.
+ /// let zähler = 10; // OK, it's still latin.
+ /// let カウンタ = 10; // Will spawn the lint.
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub DISALLOWED_SCRIPT_IDENTS,
+ restriction,
+ "usage of non-allowed Unicode scripts"
+}
+
+#[derive(Clone, Debug)]
+pub struct DisallowedScriptIdents {
+ whitelist: FxHashSet<Script>,
+}
+
+impl DisallowedScriptIdents {
+ pub fn new(whitelist: &[String]) -> Self {
+ let whitelist = whitelist
+ .iter()
+ .map(String::as_str)
+ .filter_map(Script::from_full_name)
+ .collect();
+ Self { whitelist }
+ }
+}
+
+impl_lint_pass!(DisallowedScriptIdents => [DISALLOWED_SCRIPT_IDENTS]);
+
+impl EarlyLintPass for DisallowedScriptIdents {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+ // Implementation is heavily inspired by the implementation of [`non_ascii_idents`] lint:
+ // https://github.com/rust-lang/rust/blob/master/compiler/rustc_lint/src/non_ascii_idents.rs
+
+ let check_disallowed_script_idents = cx.builder.lint_level(DISALLOWED_SCRIPT_IDENTS).0 != Level::Allow;
+ if !check_disallowed_script_idents {
+ return;
+ }
+
+ let symbols = cx.sess().parse_sess.symbol_gallery.symbols.lock();
+ // Sort by `Span` so that error messages make sense with respect to the
+ // order of identifier locations in the code.
+ let mut symbols: Vec<_> = symbols.iter().collect();
+ symbols.sort_unstable_by_key(|k| k.1);
+
+ for (symbol, &span) in &symbols {
+ // Note: `symbol.as_str()` is an expensive operation, thus should not be called
+ // more than once for a single symbol.
+ let symbol_str = symbol.as_str();
+ if symbol_str.is_ascii() {
+ continue;
+ }
+
+ for c in symbol_str.chars() {
+ // We want to iterate through all the scripts associated with this character
+ // and check whether at least of one scripts is in the whitelist.
+ let forbidden_script = c
+ .script_extension()
+ .iter()
+ .find(|script| !self.whitelist.contains(script));
+ if let Some(script) = forbidden_script {
+ span_lint(
+ cx,
+ DISALLOWED_SCRIPT_IDENTS,
+ span,
+ &format!(
++ "identifier `{symbol_str}` has a Unicode script that is not allowed by configuration: {}",
+ script.full_name()
+ ),
+ );
+ // We don't want to spawn warning multiple times over a single identifier.
+ break;
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir::{
- def::Res, def_id::DefId, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind,
- };
+use clippy_utils::diagnostics::span_lint_and_then;
+
+use rustc_data_structures::fx::FxHashMap;
- conf_disallowed: Vec<conf::DisallowedType>,
++use rustc_hir::def::{Namespace, 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 {
- pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self {
++ conf_disallowed: Vec<conf::DisallowedPath>,
+ def_ids: FxHashMap<DefId, Option<String>>,
+ prim_tys: FxHashMap<PrimTy, Option<String>>,
+}
+
+impl DisallowedTypes {
- let (path, reason) = match conf {
- conf::DisallowedType::Simple(path) => (path, None),
- conf::DisallowedType::WithReason { path, reason } => (
- path,
- reason.as_ref().map(|reason| format!("{} (from clippy.toml)", reason)),
- ),
- };
- let segs: Vec<_> = path.split("::").collect();
- match clippy_utils::def_path_res(cx, &segs) {
++ 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.def_ids.get(did) {
+ emit(cx, &cx.tcx.def_path_str(*did), span, reason.as_deref());
+ }
+ },
+ Res::PrimTy(prim) => {
+ if let Some(reason) = self.prim_tys.get(prim) {
+ emit(cx, prim.name_str(), span, reason.as_deref());
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
+
+impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for conf in &self.conf_disallowed {
- &format!("`{}` is not allowed according to config", name),
++ let segs: Vec<_> = conf.path().split("::").collect();
++ 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);
+ },
+ _ => {},
+ }
+ }
+ }
+
+ 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);
+ }
+}
+
+fn emit(cx: &LateContext<'_>, name: &str, span: Span, reason: Option<&str>) {
+ span_lint_and_then(
+ cx,
+ DISALLOWED_TYPES,
+ span,
++ &format!("`{name}` is not allowed according to config"),
+ |diag| {
+ if let Some(reason) = reason {
+ diag.note(reason);
+ }
+ },
+ );
+}
--- /dev/null
- impl_lint_pass!(DocMarkdown =>
- [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN]
- );
+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;
+use rustc_errors::{Applicability, Handler, MultiSpan, 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"
++}
++
+#[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,
+ }
+ }
+}
+
- lint_for_missing_headers(cx, item.def_id.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
++impl_lint_pass!(DocMarkdown => [
++ DOC_LINK_WITH_QUOTES,
++ DOC_MARKDOWN,
++ MISSING_SAFETY_DOC,
++ MISSING_ERRORS_DOC,
++ MISSING_PANICS_DOC,
++ NEEDLESS_DOCTEST_MAIN
++]);
+
+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());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ match item.kind {
+ hir::ItemKind::Fn(ref sig, _, body_id) => {
+ if !(is_entrypoint_fn(cx, item.def_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.def_id.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(body.value);
- lint_for_missing_headers(cx, item.def_id.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
++ lint_for_missing_headers(
++ cx,
++ item.def_id.def_id,
++ item.span,
++ sig,
++ headers,
++ Some(body_id),
++ fpu.panic_span,
++ );
+ }
+ },
+ hir::ItemKind::Impl(impl_) => {
+ self.in_trait_impl = impl_.of_trait.is_some();
+ },
+ 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",
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ 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());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
+ if !in_external_macro(cx.tcx.sess, item.span) {
+ lint_for_missing_headers(cx, item.def_id.def_id, item.span, 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());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ 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.def_id.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(body.value);
- #[derive(Copy, Clone)]
++ lint_for_missing_headers(
++ cx,
++ item.def_id.def_id,
++ item.span,
++ sig,
++ headers,
++ Some(body_id),
++ fpu.panic_span,
++ );
+ }
+ }
+}
+
+fn lint_for_missing_headers<'tcx>(
+ cx: &LateContext<'tcx>,
+ def_id: LocalDefId,
+ span: impl Into<MultiSpan> + Copy,
+ sig: &hir::FnSig<'_>,
+ headers: DocHeaders,
+ body_id: Option<hir::BodyId>,
+ panic_span: Option<Span>,
+) {
+ if !cx.access_levels.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;
+ }
+
+ if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
+ span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ span,
+ "unsafe function's docs miss `# 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)
+}
+
- return DocHeaders {
- safety: false,
- errors: false,
- panics: false,
- };
++#[derive(Copy, Clone, Default)]
+struct DocHeaders {
+ safety: bool,
+ errors: bool,
+ panics: bool,
+}
+
+fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> 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 {
+ safety: true,
+ errors: true,
+ panics: true,
+ };
+ }
+ }
+
+ 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() {
- let mut headers = DocHeaders {
- safety: false,
- errors: false,
- panics: false,
- };
++ return 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))),
+ }
+ });
+ 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};
+
- format!("`{}`", snippet),
++ 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,
+ );
+ 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
- use rustc_hir::{Expr, ExprKind, LangItem};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
++use clippy_utils::get_parent_node;
+use clippy_utils::is_must_use_func_call;
+use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
- sym::mem_drop if is_copy(cx, arg_ty) => (DROP_COPY, DROP_COPY_SUMMARY),
- sym::mem_forget if is_copy(cx, arg_ty) => (FORGET_COPY, FORGET_COPY_SUMMARY),
++use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::drop` with a reference
+ /// instead of an owned value.
+ ///
+ /// ### Why is this bad?
+ /// Calling `drop` on a reference will only drop the
+ /// reference itself, which is a no-op. It will not call the `drop` method (from
+ /// the `Drop` trait implementation) on the underlying referenced value, which
+ /// is likely what was intended.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let mut lock_guard = mutex.lock();
+ /// std::mem::drop(&lock_guard) // Should have been drop(lock_guard), mutex
+ /// // still locked
+ /// operation_that_requires_mutex_to_be_unlocked();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DROP_REF,
+ correctness,
+ "calls to `std::mem::drop` with a reference instead of an owned value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::forget` with a reference
+ /// instead of an owned value.
+ ///
+ /// ### Why is this bad?
+ /// Calling `forget` on a reference will only forget the
+ /// reference itself, which is a no-op. It will not forget the underlying
+ /// referenced
+ /// value, which is likely what was intended.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Box::new(1);
+ /// std::mem::forget(&x) // Should have been forget(x), x will still be dropped
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FORGET_REF,
+ correctness,
+ "calls to `std::mem::forget` with a reference instead of an owned value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::drop` with a value
+ /// that derives the Copy trait
+ ///
+ /// ### Why is this bad?
+ /// Calling `std::mem::drop` [does nothing for types that
+ /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html), since the
+ /// value will be copied and moved into the function on invocation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = 42; // i32 implements Copy
+ /// std::mem::drop(x) // A copy of x is passed to the function, leaving the
+ /// // original unaffected
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DROP_COPY,
+ correctness,
+ "calls to `std::mem::drop` with a value that implements Copy"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::forget` with a value that
+ /// derives the Copy trait
+ ///
+ /// ### Why is this bad?
+ /// Calling `std::mem::forget` [does nothing for types that
+ /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html) since the
+ /// value will be copied and moved into the function on invocation.
+ ///
+ /// An alternative, but also valid, explanation is that Copy types do not
+ /// implement
+ /// the Drop trait, which means they have no destructors. Without a destructor,
+ /// there
+ /// is nothing for `std::mem::forget` to ignore.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = 42; // i32 implements Copy
+ /// std::mem::forget(x) // A copy of x is passed to the function, leaving the
+ /// // original unaffected
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FORGET_COPY,
+ correctness,
+ "calls to `std::mem::forget` with a value that implements Copy"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::drop` with a value that does not implement `Drop`.
+ ///
+ /// ### Why is this bad?
+ /// Calling `std::mem::drop` is no different than dropping such a type. A different value may
+ /// have been intended.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo;
+ /// let x = Foo;
+ /// std::mem::drop(x);
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub DROP_NON_DROP,
+ suspicious,
+ "call to `std::mem::drop` with a value which does not implement `Drop`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::forget` with a value that does not implement `Drop`.
+ ///
+ /// ### Why is this bad?
+ /// Calling `std::mem::forget` is no different than dropping such a type. A different value may
+ /// have been intended.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo;
+ /// let x = Foo;
+ /// std::mem::forget(x);
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub FORGET_NON_DROP,
+ suspicious,
+ "call to `std::mem::forget` with a value which does not implement `Drop`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
+ ///
+ /// ### Why is this bad?
+ /// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
+ ///
+ /// ### Known problems
+ /// Does not catch cases if the user binds `std::mem::drop`
+ /// to a different name and calls it that way.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S;
+ /// drop(std::mem::ManuallyDrop::new(S));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct S;
+ /// unsafe {
+ /// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
+ /// }
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub UNDROPPED_MANUALLY_DROPS,
+ correctness,
+ "use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
+}
+
+const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \
+ Dropping a reference does nothing";
+const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \
+ Forgetting a reference does nothing";
+const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that implements `Copy`. \
+ Dropping a copy leaves the original intact";
+const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \
+ Forgetting a copy leaves the original intact";
+const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
+ Dropping such a type only extends its contained lifetimes";
+const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
+ Forgetting such a type is the same as dropping it";
+
+declare_lint_pass!(DropForgetRef => [
+ DROP_REF,
+ FORGET_REF,
+ DROP_COPY,
+ FORGET_COPY,
+ DROP_NON_DROP,
+ FORGET_NON_DROP,
+ UNDROPPED_MANUALLY_DROPS
+]);
+
+impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Call(path, [arg]) = expr.kind
+ && let ExprKind::Path(ref qpath) = path.kind
+ && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
+ && let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id)
+ {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
++ let is_copy = is_copy(cx, arg_ty);
++ let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr);
+ let (lint, msg) = match fn_name {
+ sym::mem_drop if arg_ty.is_ref() => (DROP_REF, DROP_REF_SUMMARY),
+ sym::mem_forget if arg_ty.is_ref() => (FORGET_REF, FORGET_REF_SUMMARY),
- || is_must_use_ty(cx, arg_ty)) =>
++ sym::mem_drop if is_copy && !drop_is_single_call_in_arm => (DROP_COPY, DROP_COPY_SUMMARY),
++ sym::mem_forget if is_copy => (FORGET_COPY, FORGET_COPY_SUMMARY),
+ sym::mem_drop if is_type_lang_item(cx, arg_ty, LangItem::ManuallyDrop) => {
+ span_lint_and_help(
+ cx,
+ UNDROPPED_MANUALLY_DROPS,
+ expr.span,
+ "the inner value of this ManuallyDrop will not be dropped",
+ None,
+ "to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
+ );
+ return;
+ }
+ sym::mem_drop
+ if !(arg_ty.needs_drop(cx.tcx, cx.param_env)
+ || is_must_use_func_call(cx, arg)
- &format!("argument has type `{}`", arg_ty),
++ || is_must_use_ty(cx, arg_ty)
++ || drop_is_single_call_in_arm
++ ) =>
+ {
+ (DROP_NON_DROP, DROP_NON_DROP_SUMMARY)
+ },
+ sym::mem_forget if !arg_ty.needs_drop(cx.tcx, cx.param_env) => {
+ (FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY)
+ },
+ _ => return,
+ };
+ span_lint_and_note(
+ cx,
+ lint,
+ expr.span,
+ msg,
+ Some(arg.span),
++ &format!("argument has type `{arg_ty}`"),
+ );
+ }
+ }
+}
++
++// dropping returned value of a function like in the following snippet is considered idiomatic, see
++// #9482 for examples match <var> {
++// <pat> => drop(fn_with_side_effect_and_returning_some_value()),
++// ..
++// }
++fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
++ if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
++ let parent_node = get_parent_node(cx.tcx, drop_expr.hir_id);
++ if let Some(Node::Arm(Arm { body, .. })) = &parent_node {
++ return body.hir_id == drop_expr.hir_id;
++ }
++ }
++ false
++}
--- /dev/null
- "if let {}::{} = {}.entry({}) {} else {}",
+use clippy_utils::higher;
+use clippy_utils::{
+ can_move_expr_to_closure_no_visit,
+ diagnostics::span_lint_and_sugg,
+ is_expr_final_block_expr, is_expr_used_or_unified, match_def_path, paths, peel_hir_expr_while,
+ source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context},
+ SpanlessEq,
+};
+use core::fmt::Write;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ hir_id::HirIdSet,
+ intravisit::{walk_expr, Visitor},
+ Block, Expr, ExprKind, Guard, HirId, Let, Pat, Stmt, StmtKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{Span, SyntaxContext, DUMMY_SP};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of `contains_key` + `insert` on `HashMap`
+ /// or `BTreeMap`.
+ ///
+ /// ### Why is this bad?
+ /// Using `entry` is more efficient.
+ ///
+ /// ### Known problems
+ /// The suggestion may have type inference errors in some cases. e.g.
+ /// ```rust
+ /// let mut map = std::collections::HashMap::new();
+ /// let _ = if !map.contains_key(&0) {
+ /// map.insert(0, 0)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # let mut map = HashMap::new();
+ /// # let k = 1;
+ /// # let v = 1;
+ /// if !map.contains_key(&k) {
+ /// map.insert(k, v);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # let mut map = HashMap::new();
+ /// # let k = 1;
+ /// # let v = 1;
+ /// map.entry(k).or_insert(v);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MAP_ENTRY,
+ perf,
+ "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`"
+}
+
+declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
+
+impl<'tcx> LateLintPass<'tcx> for HashMapPass {
+ #[expect(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (cond_expr, then_expr, else_expr) = match higher::If::hir(expr) {
+ Some(higher::If { cond, then, r#else }) => (cond, then, r#else),
+ _ => return,
+ };
+
+ let (map_ty, contains_expr) = match try_parse_contains(cx, cond_expr) {
+ Some(x) => x,
+ None => return,
+ };
+
+ let then_search = match find_insert_calls(cx, &contains_expr, then_expr) {
+ Some(x) => x,
+ None => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let map_str = snippet_with_context(cx, contains_expr.map.span, contains_expr.call_ctxt, "..", &mut app).0;
+ let key_str = snippet_with_context(cx, contains_expr.key.span, contains_expr.call_ctxt, "..", &mut app).0;
+ let sugg = if let Some(else_expr) = else_expr {
+ let else_search = match find_insert_calls(cx, &contains_expr, else_expr) {
+ Some(search) => search,
+ None => return,
+ };
+
+ if then_search.edits.is_empty() && else_search.edits.is_empty() {
+ // No insertions
+ return;
+ } else if then_search.edits.is_empty() || else_search.edits.is_empty() {
+ // if .. { insert } else { .. } or if .. { .. } else { insert }
+ let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
+ (true, true) => (
+ then_search.snippet_vacant(cx, then_expr.span, &mut app),
+ snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
+ ),
+ (true, false) => (
+ then_search.snippet_occupied(cx, then_expr.span, &mut app),
+ snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
+ ),
+ (false, true) => (
+ else_search.snippet_occupied(cx, else_expr.span, &mut app),
+ snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
+ ),
+ (false, false) => (
+ else_search.snippet_vacant(cx, else_expr.span, &mut app),
+ snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
+ ),
+ };
+ format!(
- entry_kind,
- map_str,
- key_str,
- then_str,
- else_str,
++ "if let {}::{entry_kind} = {map_str}.entry({key_str}) {then_str} else {else_str}",
+ map_ty.entry_path(),
- "match {}.entry({}) {{\n{indent} {entry}::{} => {}\n\
- {indent} {entry}::{} => {}\n{indent}}}",
- map_str,
- key_str,
- then_entry,
+ )
+ } else {
+ // if .. { insert } else { insert }
+ let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
+ (
+ then_search.snippet_vacant(cx, then_expr.span, &mut app),
+ else_search.snippet_occupied(cx, else_expr.span, &mut app),
+ )
+ } else {
+ (
+ then_search.snippet_occupied(cx, then_expr.span, &mut app),
+ else_search.snippet_vacant(cx, else_expr.span, &mut app),
+ )
+ };
+ let indent_str = snippet_indent(cx, expr.span);
+ let indent_str = indent_str.as_deref().unwrap_or("");
+ format!(
- else_entry,
++ "match {map_str}.entry({key_str}) {{\n{indent_str} {entry}::{then_entry} => {}\n\
++ {indent_str} {entry}::{else_entry} => {}\n{indent_str}}}",
+ reindent_multiline(then_str.into(), true, Some(4 + indent_str.len())),
- indent = indent_str,
+ reindent_multiline(else_str.into(), true, Some(4 + indent_str.len())),
+ entry = map_ty.entry_path(),
- "if let {}::{} = {}.entry({}) {}",
+ )
+ }
+ } else {
+ if then_search.edits.is_empty() {
+ // no insertions
+ return;
+ }
+
+ // if .. { insert }
+ if !then_search.allow_insert_closure {
+ let (body_str, entry_kind) = if contains_expr.negated {
+ then_search.snippet_vacant(cx, then_expr.span, &mut app)
+ } else {
+ then_search.snippet_occupied(cx, then_expr.span, &mut app)
+ };
+ format!(
- entry_kind,
- map_str,
- key_str,
- body_str,
++ "if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}",
+ map_ty.entry_path(),
- format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, value_str)
+ )
+ } else if let Some(insertion) = then_search.as_single_insertion() {
+ let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
+ if contains_expr.negated {
+ if insertion.value.can_have_side_effects() {
- format!("{}.entry({}).or_insert({});", map_str, key_str, value_str)
++ format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});")
+ } else {
- format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, block_str)
++ format!("{map_str}.entry({key_str}).or_insert({value_str});")
+ }
+ } else {
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
+ // This would need to be a different lint.
+ return;
+ }
+ } else {
+ let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
+ if contains_expr.negated {
++ format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});")
+ } else {
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
+ // This would need to be a different lint.
+ return;
+ }
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MAP_ENTRY,
+ expr.span,
+ &format!("usage of `contains_key` followed by `insert` on a `{}`", map_ty.name()),
+ "try this",
+ sugg,
+ app,
+ );
+ }
+}
+
+#[derive(Clone, Copy)]
+enum MapType {
+ Hash,
+ BTree,
+}
+impl MapType {
+ fn name(self) -> &'static str {
+ match self {
+ Self::Hash => "HashMap",
+ Self::BTree => "BTreeMap",
+ }
+ }
+ fn entry_path(self) -> &'static str {
+ match self {
+ Self::Hash => "std::collections::hash_map::Entry",
+ Self::BTree => "std::collections::btree_map::Entry",
+ }
+ }
+}
+
+struct ContainsExpr<'tcx> {
+ negated: bool,
+ map: &'tcx Expr<'tcx>,
+ key: &'tcx Expr<'tcx>,
+ call_ctxt: SyntaxContext,
+}
+fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(MapType, ContainsExpr<'tcx>)> {
+ let mut negated = false;
+ let expr = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::Unary(UnOp::Not, e) => {
+ negated = !negated;
+ Some(e)
+ },
+ _ => None,
+ });
+ match expr.kind {
+ ExprKind::MethodCall(
+ _,
+ map,
+ [
+ Expr {
+ kind: ExprKind::AddrOf(_, _, key),
+ span: key_span,
+ ..
+ },
+ ],
+ _,
+ ) if key_span.ctxt() == expr.span.ctxt() => {
+ let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ let expr = ContainsExpr {
+ negated,
+ map,
+ key,
+ call_ctxt: expr.span.ctxt(),
+ };
+ if match_def_path(cx, id, &paths::BTREEMAP_CONTAINS_KEY) {
+ Some((MapType::BTree, expr))
+ } else if match_def_path(cx, id, &paths::HASHMAP_CONTAINS_KEY) {
+ Some((MapType::Hash, expr))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+struct InsertExpr<'tcx> {
+ map: &'tcx Expr<'tcx>,
+ key: &'tcx Expr<'tcx>,
+ value: &'tcx Expr<'tcx>,
+}
+fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
+ if let ExprKind::MethodCall(_, map, [key, value], _) = expr.kind {
+ let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ if match_def_path(cx, id, &paths::BTREEMAP_INSERT) || match_def_path(cx, id, &paths::HASHMAP_INSERT) {
+ Some(InsertExpr { map, key, value })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+/// An edit that will need to be made to move the expression to use the entry api
+#[derive(Clone, Copy)]
+enum Edit<'tcx> {
+ /// A semicolon that needs to be removed. Used to create a closure for `insert_with`.
+ RemoveSemi(Span),
+ /// An insertion into the map.
+ Insertion(Insertion<'tcx>),
+}
+impl<'tcx> Edit<'tcx> {
+ fn as_insertion(self) -> Option<Insertion<'tcx>> {
+ if let Self::Insertion(i) = self { Some(i) } else { None }
+ }
+}
+#[derive(Clone, Copy)]
+struct Insertion<'tcx> {
+ call: &'tcx Expr<'tcx>,
+ value: &'tcx Expr<'tcx>,
+}
+
+/// This visitor needs to do a multiple things:
+/// * Find all usages of the map. An insertion can only be made before any other usages of the map.
+/// * Determine if there's an insertion using the same key. There's no need for the entry api
+/// otherwise.
+/// * Determine if the final statement executed is an insertion. This is needed to use
+/// `or_insert_with`.
+/// * Determine if there's any sub-expression that can't be placed in a closure.
+/// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
+#[expect(clippy::struct_excessive_bools)]
+struct InsertSearcher<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ /// The map expression used in the contains call.
+ map: &'tcx Expr<'tcx>,
+ /// The key expression used in the contains call.
+ key: &'tcx Expr<'tcx>,
+ /// The context of the top level block. All insert calls must be in the same context.
+ ctxt: SyntaxContext,
+ /// Whether this expression can be safely moved into a closure.
+ allow_insert_closure: bool,
+ /// Whether this expression can use the entry api.
+ can_use_entry: bool,
+ /// Whether this expression is the final expression in this code path. This may be a statement.
+ in_tail_pos: bool,
+ // Is this expression a single insert. A slightly better suggestion can be made in this case.
+ is_single_insert: bool,
+ /// If the visitor has seen the map being used.
+ is_map_used: bool,
+ /// The locations where changes need to be made for the suggestion.
+ edits: Vec<Edit<'tcx>>,
+ /// A stack of loops the visitor is currently in.
+ loops: Vec<HirId>,
+ /// Local variables created in the expression. These don't need to be captured.
+ locals: HirIdSet,
+}
+impl<'tcx> InsertSearcher<'_, 'tcx> {
+ /// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
+ /// only if they are on separate code paths. This will return whether the map was used in the
+ /// given expression.
+ fn visit_cond_arm(&mut self, e: &'tcx Expr<'_>) -> bool {
+ let is_map_used = self.is_map_used;
+ let in_tail_pos = self.in_tail_pos;
+ self.visit_expr(e);
+ let res = self.is_map_used;
+ self.is_map_used = is_map_used;
+ self.in_tail_pos = in_tail_pos;
+ res
+ }
+
+ /// Visits an expression which is not itself in a tail position, but other sibling expressions
+ /// may be. e.g. if conditions
+ fn visit_non_tail_expr(&mut self, e: &'tcx Expr<'_>) {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ self.visit_expr(e);
+ self.in_tail_pos = in_tail_pos;
+ }
+}
+impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Semi(e) => {
+ self.visit_expr(e);
+
+ if self.in_tail_pos && self.allow_insert_closure {
+ // The spans are used to slice the top level expression into multiple parts. This requires that
+ // they all come from the same part of the source code.
+ if stmt.span.ctxt() == self.ctxt && e.span.ctxt() == self.ctxt {
+ self.edits
+ .push(Edit::RemoveSemi(stmt.span.trim_start(e.span).unwrap_or(DUMMY_SP)));
+ } else {
+ self.allow_insert_closure = false;
+ }
+ }
+ },
+ StmtKind::Expr(e) => self.visit_expr(e),
+ StmtKind::Local(l) => {
+ self.visit_pat(l.pat);
+ if let Some(e) = l.init {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.in_tail_pos = false;
+ self.is_single_insert = false;
+ self.visit_expr(e);
+ }
+ },
+ StmtKind::Item(_) => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.is_single_insert = false;
+ },
+ }
+ }
+
+ fn visit_block(&mut self, block: &'tcx Block<'_>) {
+ // If the block is in a tail position, then the last expression (possibly a statement) is in the
+ // tail position. The rest, however, are not.
+ match (block.stmts, block.expr) {
+ ([], None) => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ },
+ ([], Some(expr)) => self.visit_expr(expr),
+ (stmts, Some(expr)) => {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ for stmt in stmts {
+ self.visit_stmt(stmt);
+ }
+ self.in_tail_pos = in_tail_pos;
+ self.visit_expr(expr);
+ },
+ ([stmts @ .., stmt], None) => {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ for stmt in stmts {
+ self.visit_stmt(stmt);
+ }
+ self.in_tail_pos = in_tail_pos;
+ self.visit_stmt(stmt);
+ },
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if !self.can_use_entry {
+ return;
+ }
+
+ match try_parse_insert(self.cx, expr) {
+ Some(insert_expr) if SpanlessEq::new(self.cx).eq_expr(self.map, insert_expr.map) => {
+ // Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
+ if self.is_map_used
+ || !SpanlessEq::new(self.cx).eq_expr(self.key, insert_expr.key)
+ || expr.span.ctxt() != self.ctxt
+ {
+ self.can_use_entry = false;
+ return;
+ }
+
+ self.edits.push(Edit::Insertion(Insertion {
+ call: expr,
+ value: insert_expr.value,
+ }));
+ self.is_map_used = true;
+ self.allow_insert_closure &= self.in_tail_pos;
+
+ // The value doesn't affect whether there is only a single insert expression.
+ let is_single_insert = self.is_single_insert;
+ self.visit_non_tail_expr(insert_expr.value);
+ self.is_single_insert = is_single_insert;
+ },
+ _ if SpanlessEq::new(self.cx).eq_expr(self.map, expr) => {
+ self.is_map_used = true;
+ },
+ _ => match expr.kind {
+ ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
+ self.is_single_insert = false;
+ self.visit_non_tail_expr(cond_expr);
+ // Each branch may contain it's own insert expression.
+ let mut is_map_used = self.visit_cond_arm(then_expr);
+ is_map_used |= self.visit_cond_arm(else_expr);
+ self.is_map_used = is_map_used;
+ },
+ ExprKind::Match(scrutinee_expr, arms, _) => {
+ self.is_single_insert = false;
+ self.visit_non_tail_expr(scrutinee_expr);
+ // Each branch may contain it's own insert expression.
+ let mut is_map_used = self.is_map_used;
+ for arm in arms {
+ self.visit_pat(arm.pat);
+ if let Some(Guard::If(guard) | Guard::IfLet(&Let { init: guard, .. })) = arm.guard {
+ self.visit_non_tail_expr(guard);
+ }
+ is_map_used |= self.visit_cond_arm(arm.body);
+ }
+ self.is_map_used = is_map_used;
+ },
+ ExprKind::Loop(block, ..) => {
+ self.loops.push(expr.hir_id);
+ self.is_single_insert = false;
+ self.allow_insert_closure &= !self.in_tail_pos;
+ // Don't allow insertions inside of a loop.
+ let edit_len = self.edits.len();
+ self.visit_block(block);
+ if self.edits.len() != edit_len {
+ self.can_use_entry = false;
+ }
+ self.loops.pop();
+ },
+ ExprKind::Block(block, _) => self.visit_block(block),
+ ExprKind::InlineAsm(_) => {
+ self.can_use_entry = false;
+ },
+ _ => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.allow_insert_closure &=
+ can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops, &self.locals);
+ // Sub expressions are no longer in the tail position.
+ self.is_single_insert = false;
+ self.in_tail_pos = false;
+ walk_expr(self, expr);
+ },
+ },
+ }
+ }
+
+ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
+ p.each_binding_or_first(&mut |_, id, _, _| {
+ self.locals.insert(id);
+ });
+ }
+}
+
+struct InsertSearchResults<'tcx> {
+ edits: Vec<Edit<'tcx>>,
+ allow_insert_closure: bool,
+ is_single_insert: bool,
+}
+impl<'tcx> InsertSearchResults<'tcx> {
+ fn as_single_insertion(&self) -> Option<Insertion<'tcx>> {
+ self.is_single_insert.then(|| self.edits[0].as_insertion().unwrap())
+ }
+
+ fn snippet(
+ &self,
+ cx: &LateContext<'_>,
+ mut span: Span,
+ app: &mut Applicability,
+ write_wrapped: impl Fn(&mut String, Insertion<'_>, SyntaxContext, &mut Applicability),
+ ) -> String {
+ let ctxt = span.ctxt();
+ let mut res = String::new();
+ for insertion in self.edits.iter().filter_map(|e| e.as_insertion()) {
+ res.push_str(&snippet_with_applicability(
+ cx,
+ span.until(insertion.call.span),
+ "..",
+ app,
+ ));
+ if is_expr_used_or_unified(cx.tcx, insertion.call) {
+ write_wrapped(&mut res, insertion, ctxt, app);
+ } else {
+ let _ = write!(
+ res,
+ "e.insert({})",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
+ );
+ }
+ span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
+ }
+ res.push_str(&snippet_with_applicability(cx, span, "..", app));
+ res
+ }
+
+ fn snippet_occupied(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
+ (
+ self.snippet(cx, span, app, |res, insertion, ctxt, app| {
+ // Insertion into a map would return `Some(&mut value)`, but the entry returns `&mut value`
+ let _ = write!(
+ res,
+ "Some(e.insert({}))",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
+ );
+ }),
+ "Occupied(mut e)",
+ )
+ }
+
+ fn snippet_vacant(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
+ (
+ self.snippet(cx, span, app, |res, insertion, ctxt, app| {
+ // Insertion into a map would return `None`, but the entry returns a mutable reference.
+ let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) {
+ write!(
+ res,
+ "e.insert({});\n{}None",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
+ snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
+ )
+ } else {
+ write!(
+ res,
+ "{{ e.insert({}); None }}",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
+ )
+ };
+ }),
+ "Vacant(e)",
+ )
+ }
+
+ fn snippet_closure(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
+ let ctxt = span.ctxt();
+ let mut res = String::new();
+ for edit in &self.edits {
+ match *edit {
+ Edit::Insertion(insertion) => {
+ // Cut out the value from `map.insert(key, value)`
+ res.push_str(&snippet_with_applicability(
+ cx,
+ span.until(insertion.call.span),
+ "..",
+ app,
+ ));
+ res.push_str(&snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0);
+ span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
+ },
+ Edit::RemoveSemi(semi_span) => {
+ // Cut out the semicolon. This allows the value to be returned from the closure.
+ res.push_str(&snippet_with_applicability(cx, span.until(semi_span), "..", app));
+ span = span.trim_start(semi_span).unwrap_or(DUMMY_SP);
+ },
+ }
+ }
+ res.push_str(&snippet_with_applicability(cx, span, "..", app));
+ res
+ }
+}
+
+fn find_insert_calls<'tcx>(
+ cx: &LateContext<'tcx>,
+ contains_expr: &ContainsExpr<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<InsertSearchResults<'tcx>> {
+ let mut s = InsertSearcher {
+ cx,
+ map: contains_expr.map,
+ key: contains_expr.key,
+ ctxt: expr.span.ctxt(),
+ edits: Vec::new(),
+ is_map_used: false,
+ allow_insert_closure: true,
+ can_use_entry: true,
+ in_tail_pos: true,
+ is_single_insert: true,
+ loops: Vec::new(),
+ locals: HirIdSet::default(),
+ };
+ s.visit_expr(expr);
+ let allow_insert_closure = s.allow_insert_closure;
+ let is_single_insert = s.is_single_insert;
+ let edits = s.edits;
+ s.can_use_entry.then_some(InsertSearchResults {
+ edits,
+ allow_insert_closure,
+ is_single_insert,
+ })
+}
--- /dev/null
- &format!("all variants have the same {}fix: `{}`", what, value),
+//! 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,
- "remove the {}fixes and use full paths to \
- the variants instead of glob imports",
- what
++ &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(&(ref mod_name, ref 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.def_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.access_levels.is_exported(item.def_id.def_id)) {
+ check_variant(cx, self.threshold, def, item_name, item.span);
+ }
+ }
+ self.modules.push((item.ident.name, item_camel));
+ }
+}
--- /dev/null
- PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => {
- !etc.as_opt_usize().is_some() && array_rec(a)
- }
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::implements_trait;
+use if_chain::if_chain;
+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_chain! {
+ if !in_external_macro(cx.sess(), expr.span);
+ if let ExprKind::Let(let_expr) = expr.kind;
+ if 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);
+ if is_structural_partial_eq(cx, exp_ty, pat_ty);
+ then {
+
+ let mut applicability = Applicability::MachineApplicable;
+ 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,
++ "{} == {pat_str}",
+ snippet_with_context(cx, let_expr.init.span, expr.span.ctxt(), "..", &mut applicability).0,
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+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_analysis::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;
- fn fake_read(&mut self, _: &rustc_hir_analysis::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+
+#[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.def_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);
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ 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_analysis::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 clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::higher::VecArgs;
+use clippy_utils::source::snippet_opt;
- use rustc_middle::ty::{self, ClosureKind, Ty, TypeVisitable};
++use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::usage::local_used_after_expr;
+use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
+use rustc_middle::ty::binding::BindingMode;
- if_chain! {
- if let ty::Closure(_, substs) = callee_ty.peel_refs().kind();
- if substs.as_closure().kind() == ClosureKind::FnMut;
- if path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr));
-
- then {
++use rustc_middle::ty::{self, Ty, TypeVisitable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for closures which just call another function where
+ /// the function can be called directly. `unsafe` functions or calls where types
+ /// get adjusted are ignored.
+ ///
+ /// ### Why is this bad?
+ /// Needlessly creating a closure adds code for no benefit
+ /// and gives the optimizer more work.
+ ///
+ /// ### Known problems
+ /// If creating the closure inside the closure has a side-
+ /// effect then moving the closure creation out will change when that side-
+ /// effect runs.
+ /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// xs.map(|x| foo(x))
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// // where `foo(_)` is a plain function that takes the exact argument type of `x`.
+ /// xs.map(foo)
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub REDUNDANT_CLOSURE,
+ style,
+ "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for closures which only invoke a method on the closure
+ /// argument and can be replaced by referencing the method directly.
+ ///
+ /// ### Why is this bad?
+ /// It's unnecessary to create the closure.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// Some('a').map(|s| s.to_uppercase());
+ /// ```
+ /// may be rewritten as
+ /// ```rust,ignore
+ /// Some('a').map(char::to_uppercase);
+ /// ```
+ #[clippy::version = "1.35.0"]
+ pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ pedantic,
+ "redundant closures for method calls"
+}
+
+declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]);
+
+impl<'tcx> LateLintPass<'tcx> for EtaReduction {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+ let body = match expr.kind {
+ ExprKind::Closure(&Closure { body, .. }) => cx.tcx.hir().body(body),
+ _ => return,
+ };
+ if body.value.span.from_expansion() {
+ if body.params.is_empty() {
+ if let Some(VecArgs::Vec(&[])) = higher::VecArgs::hir(cx, body.value) {
+ // replace `|| vec![]` with `Vec::new`
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_CLOSURE,
+ expr.span,
+ "redundant closure",
+ "replace the closure with `Vec::new`",
+ "std::vec::Vec::new".into(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ // skip `foo(|| macro!())`
+ return;
+ }
+
+ let closure_ty = cx.typeck_results().expr_ty(expr);
+
+ if_chain!(
+ if !is_adjusted(cx, body.value);
+ if let ExprKind::Call(callee, args) = body.value.kind;
+ if let ExprKind::Path(_) = callee.kind;
+ if check_inputs(cx, body.params, None, args);
+ let callee_ty = cx.typeck_results().expr_ty_adjusted(callee);
+ let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id)
+ .map_or(callee_ty, |id| cx.tcx.type_of(id));
+ if check_sig(cx, closure_ty, call_ty);
+ let substs = cx.typeck_results().node_substs(callee.hir_id);
+ // This fixes some false positives that I don't entirely understand
+ if substs.is_empty() || !cx.typeck_results().expr_ty(expr).has_late_bound_regions();
+ // A type param function ref like `T::f` is not 'static, however
+ // it is if cast like `T::f as fn()`. This seems like a rustc bug.
+ if !substs.types().any(|t| matches!(t.kind(), ty::Param(_)));
+ let callee_ty_unadjusted = cx.typeck_results().expr_ty(callee).peel_refs();
+ if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Arc);
+ if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Rc);
+ then {
+ span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
+ if let Some(mut snippet) = snippet_opt(cx, callee.span) {
- snippet = format!("&mut {}", snippet);
- }
++ if let Some(fn_mut_id) = cx.tcx.lang_items().fn_mut_trait()
++ && implements_trait(cx, callee_ty.peel_refs(), fn_mut_id, &[])
++ && path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr))
++ {
+ // Mutable closure is used after current expr; we cannot consume it.
- format!("{}::{}", name, path.ident.name),
++ snippet = format!("&mut {snippet}");
+ }
+ diag.span_suggestion(
+ expr.span,
+ "replace the closure with the function itself",
+ snippet,
+ Applicability::MachineApplicable,
+ );
+ }
+ });
+ }
+ );
+
+ if_chain!(
+ if !is_adjusted(cx, body.value);
+ if let ExprKind::MethodCall(path, receiver, args, _) = body.value.kind;
+ if check_inputs(cx, body.params, Some(receiver), args);
+ let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap();
+ let substs = cx.typeck_results().node_substs(body.value.hir_id);
+ let call_ty = cx.tcx.bound_type_of(method_def_id).subst(cx.tcx, substs);
+ if check_sig(cx, closure_ty, call_ty);
+ then {
+ span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| {
+ let name = get_ufcs_type_name(cx, method_def_id);
+ diag.span_suggestion(
+ expr.span,
+ "replace the closure with the method itself",
++ format!("{name}::{}", path.ident.name),
+ Applicability::MachineApplicable,
+ );
+ })
+ }
+ );
+ }
+}
+
+fn check_inputs(
+ cx: &LateContext<'_>,
+ params: &[Param<'_>],
+ receiver: Option<&Expr<'_>>,
+ call_args: &[Expr<'_>],
+) -> bool {
+ if receiver.map_or(params.len() != call_args.len(), |_| params.len() != call_args.len() + 1) {
+ return false;
+ }
+ let binding_modes = cx.typeck_results().pat_binding_modes();
+ let check_inputs = |param: &Param<'_>, arg| {
+ match param.pat.kind {
+ PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {},
+ _ => return false,
+ }
+ // checks that parameters are not bound as `ref` or `ref mut`
+ if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) {
+ return false;
+ }
+
+ match *cx.typeck_results().expr_adjustments(arg) {
+ [] => true,
+ [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Borrow(AutoBorrow::Ref(_, mu2)),
+ ..
+ },
+ ] => {
+ // re-borrow with the same mutability is allowed
+ let ty = cx.typeck_results().expr_ty(arg);
+ matches!(*ty.kind(), ty::Ref(.., mu1) if mu1 == mu2.into())
+ },
+ _ => false,
+ }
+ };
+ std::iter::zip(params, receiver.into_iter().chain(call_args.iter())).all(|(param, arg)| check_inputs(param, arg))
+}
+
+fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tcx>) -> bool {
+ let call_sig = call_ty.fn_sig(cx.tcx);
+ if call_sig.unsafety() == Unsafety::Unsafe {
+ return false;
+ }
+ if !closure_ty.has_late_bound_regions() {
+ return true;
+ }
+ let substs = match closure_ty.kind() {
+ ty::Closure(_, substs) => substs,
+ _ => return false,
+ };
+ let closure_sig = cx.tcx.signature_unclosure(substs.as_closure().sig(), Unsafety::Normal);
+ cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig)
+}
+
+fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {
+ let assoc_item = cx.tcx.associated_item(method_def_id);
+ let def_id = assoc_item.container_id(cx.tcx);
+ match assoc_item.container {
+ ty::TraitContainer => cx.tcx.def_path_str(def_id),
+ ty::ImplContainer => {
+ let ty = cx.tcx.type_of(def_id);
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
+ _ => ty.to_string(),
+ }
+ },
+ }
+}
--- /dev/null
- let sugg = format!("#[non_exhaustive]\n{}", indent);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::indent_of;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]`
+ ///
+ /// ### Why is this bad?
+ /// Exhaustive enums are typically fine, but a project which does
+ /// not wish to make a stability commitment around exported enums may wish to
+ /// disable them by default.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Foo {
+ /// Bar,
+ /// Baz
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// enum Foo {
+ /// Bar,
+ /// Baz
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub EXHAUSTIVE_ENUMS,
+ restriction,
+ "detects exported enums that have not been marked #[non_exhaustive]"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns on any exported `structs`s that are not tagged `#[non_exhaustive]`
+ ///
+ /// ### Why is this bad?
+ /// Exhaustive structs are typically fine, but a project which does
+ /// not wish to make a stability commitment around exported structs may wish to
+ /// disable them by default.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo {
+ /// bar: u8,
+ /// baz: String,
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// struct Foo {
+ /// bar: u8,
+ /// baz: String,
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub EXHAUSTIVE_STRUCTS,
+ restriction,
+ "detects exported structs that have not been marked #[non_exhaustive]"
+}
+
+declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]);
+
+impl LateLintPass<'_> for ExhaustiveItems {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if_chain! {
+ if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind;
+ if cx.access_levels.is_exported(item.def_id.def_id);
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
+ then {
+ let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
+ if v.fields().iter().any(|f| {
+ let def_id = cx.tcx.hir().local_def_id(f.hir_id);
+ !cx.tcx.visibility(def_id).is_public()
+ }) {
+ // skip structs with private fields
+ return;
+ }
+ (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
+ } else {
+ (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
+ };
+ let suggestion_span = item.span.shrink_to_lo();
+ let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
+ span_lint_and_then(
+ cx,
+ lint,
+ item.span,
+ msg,
+ |diag| {
++ let sugg = format!("#[non_exhaustive]\n{indent}");
+ diag.span_suggestion(suggestion_span,
+ "try adding #[non_exhaustive]",
+ sugg,
+ Applicability::MaybeIncorrect);
+ }
+ );
+
+ }
+ }
+ }
+}
--- /dev/null
- format!("{}!({}(), ...)", macro_name, dest_name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::macros::FormatArgsExpn;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{is_expn_of, match_function_call, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `write!()` / `writeln()!` which can be
+ /// replaced with `(e)print!()` / `(e)println!()`
+ ///
+ /// ### Why is this bad?
+ /// Using `(e)println! is clearer and more concise
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::io::Write;
+ /// # let bar = "furchtbar";
+ /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();
+ /// writeln!(&mut std::io::stdout(), "foo: {:?}", bar).unwrap();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::io::Write;
+ /// # let bar = "furchtbar";
+ /// eprintln!("foo: {:?}", bar);
+ /// println!("foo: {:?}", bar);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_WRITE,
+ complexity,
+ "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
+}
+
+declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
+
+impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // match call to unwrap
+ if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind;
+ if unwrap_fun.ident.name == sym::unwrap;
+ // match call to write_fmt
+ if let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind);
+ if write_fun.ident.name == sym!(write_fmt);
+ // match calls to std::io::stdout() / std::io::stderr ()
+ if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
+ Some("stdout")
+ } else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
+ Some("stderr")
+ } else {
+ None
+ };
+ if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
+ then {
+ let calling_macro =
+ // ordering is important here, since `writeln!` uses `write!` internally
+ if is_expn_of(write_call.span, "writeln").is_some() {
+ Some("writeln")
+ } else if is_expn_of(write_call.span, "write").is_some() {
+ Some("write")
+ } else {
+ None
+ };
+ let prefix = if dest_name == "stderr" {
+ "e"
+ } else {
+ ""
+ };
+
+ // We need to remove the last trailing newline from the string because the
+ // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
+ // used.
+ let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
+ (
- format!("{}().write_fmt(...)", dest_name),
++ format!("{macro_name}!({dest_name}(), ...)"),
+ macro_name.replace("write", "print"),
+ )
+ } else {
+ (
- &format!("use of `{}.unwrap()`", used),
++ format!("{dest_name}().write_fmt(...)"),
+ "print".into(),
+ )
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let inputs_snippet = snippet_with_applicability(
+ cx,
+ format_args.inputs_span(),
+ "..",
+ &mut applicability,
+ );
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_WRITE,
+ expr.span,
- format!("{}{}!({})", prefix, sugg_mac, inputs_snippet),
++ &format!("use of `{used}.unwrap()`"),
+ "try this",
++ format!("{prefix}{sugg_mac}!({inputs_snippet})"),
+ applicability,
+ )
+ }
+ }
+ }
+}
+
+/// If `kind` is a block that looks like `{ let result = $expr; result }` then
+/// returns $expr. Otherwise returns `kind`.
+fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) -> &'tcx ExprKind<'hir> {
+ if_chain! {
+ if let ExprKind::Block(block, _label @ None) = kind;
+ if let Block {
+ stmts: [Stmt { kind: StmtKind::Local(local), .. }],
+ expr: Some(expr_end_of_block),
+ rules: BlockCheckMode::DefaultBlock,
+ ..
+ } = block;
+
+ // Find id of the local that expr_end_of_block resolves to
+ if let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind;
+ if let Res::Local(expr_res) = expr_path.res;
+ if let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res);
+
+ // Find id of the local we found in the block
+ if let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind;
+
+ // If those two are the same hir id
+ if res_pat.hir_id == local_hir_id;
+
+ if let Some(init) = local.init;
+ then {
+ return &init.kind;
+ }
+ }
+ kind
+}
--- /dev/null
- Self::LowerExp => format!("{:e}", f),
- Self::UpperExp => format!("{:E}", f),
- Self::Normal => format!("{}", f),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::numeric_literal;
+use if_chain::if_chain;
+use rustc_ast::ast::{self, LitFloatType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, FloatTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::fmt;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for float literals with a precision greater
+ /// than that supported by the underlying type.
+ ///
+ /// ### Why is this bad?
+ /// Rust will truncate the literal silently.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let v: f32 = 0.123_456_789_9;
+ /// println!("{}", v); // 0.123_456_789
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let v: f64 = 0.123_456_789_9;
+ /// println!("{}", v); // 0.123_456_789_9
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EXCESSIVE_PRECISION,
+ style,
+ "excessive precision for float literal"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for whole number float literals that
+ /// cannot be represented as the underlying type without loss.
+ ///
+ /// ### Why is this bad?
+ /// Rust will silently lose precision during
+ /// conversion to a float.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _: f32 = 16_777_217.0; // 16_777_216.0
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let _: f32 = 16_777_216.0;
+ /// let _: f64 = 16_777_217.0;
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub LOSSY_FLOAT_LITERAL,
+ restriction,
+ "lossy whole number float literals"
+}
+
+declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
+
+impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ if let ty::Float(fty) = *ty.kind();
+ if let hir::ExprKind::Lit(ref lit) = expr.kind;
+ if let LitKind::Float(sym, lit_float_ty) = lit.node;
+ then {
+ let sym_str = sym.as_str();
+ let formatter = FloatFormat::new(sym_str);
+ // Try to bail out if the float is for sure fine.
+ // If its within the 2 decimal digits of being out of precision we
+ // check if the parsed representation is the same as the string
+ // since we'll need the truncated string anyway.
+ let digits = count_digits(sym_str);
+ let max = max_digits(fty);
+ let type_suffix = match lit_float_ty {
+ LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
+ LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
+ LitFloatType::Unsuffixed => None
+ };
+ let (is_whole, mut float_str) = match fty {
+ FloatTy::F32 => {
+ let value = sym_str.parse::<f32>().unwrap();
+
+ (value.fract() == 0.0, formatter.format(value))
+ },
+ FloatTy::F64 => {
+ let value = sym_str.parse::<f64>().unwrap();
+
+ (value.fract() == 0.0, formatter.format(value))
+ },
+ };
+
+ if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
+ // Normalize the literal by stripping the fractional portion
+ if sym_str.split('.').next().unwrap() != float_str {
+ // If the type suffix is missing the suggestion would be
+ // incorrectly interpreted as an integer so adding a `.0`
+ // suffix to prevent that.
+ if type_suffix.is_none() {
+ float_str.push_str(".0");
+ }
+
+ span_lint_and_sugg(
+ cx,
+ LOSSY_FLOAT_LITERAL,
+ expr.span,
+ "literal cannot be represented as the underlying type without loss of precision",
+ "consider changing the type or replacing it with",
+ numeric_literal::format(&float_str, type_suffix, true),
+ Applicability::MachineApplicable,
+ );
+ }
+ } else if digits > max as usize && float_str.len() < sym_str.len() {
+ span_lint_and_sugg(
+ cx,
+ EXCESSIVE_PRECISION,
+ expr.span,
+ "float has excessive precision",
+ "consider changing the type or truncating it to",
+ numeric_literal::format(&float_str, type_suffix, true),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+#[must_use]
+fn max_digits(fty: FloatTy) -> u32 {
+ match fty {
+ FloatTy::F32 => f32::DIGITS,
+ FloatTy::F64 => f64::DIGITS,
+ }
+}
+
+/// Counts the digits excluding leading zeros
+#[must_use]
+fn count_digits(s: &str) -> usize {
+ // Note that s does not contain the f32/64 suffix, and underscores have been stripped
+ s.chars()
+ .filter(|c| *c != '-' && *c != '.')
+ .take_while(|c| *c != 'e' && *c != 'E')
+ .fold(0, |count, c| {
+ // leading zeros
+ if c == '0' && count == 0 { count } else { count + 1 }
+ })
+}
+
+enum FloatFormat {
+ LowerExp,
+ UpperExp,
+ Normal,
+}
+impl FloatFormat {
+ #[must_use]
+ fn new(s: &str) -> Self {
+ s.chars()
+ .find_map(|x| match x {
+ 'e' => Some(Self::LowerExp),
+ 'E' => Some(Self::UpperExp),
+ _ => None,
+ })
+ .unwrap_or(Self::Normal)
+ }
+ fn format<T>(&self, f: T) -> String
+ where
+ T: fmt::UpperExp + fmt::LowerExp + fmt::Display,
+ {
+ match self {
++ Self::LowerExp => format!("{f:e}"),
++ Self::UpperExp => format!("{f:E}"),
++ Self::Normal => format!("{f}"),
+ }
+ }
+}
--- /dev/null
- "{}{}{}",
- suggestion,
+use clippy_utils::consts::{
+ constant, constant_simple, Constant,
+ Constant::{Int, F32, F64},
+};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::{eq_expr_value, get_parent_expr, in_constant, numeric_literal, peel_blocks, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
+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_ast::ast;
+use std::f32::consts as f32_consts;
+use std::f64::consts as f64_consts;
+use sugg::Sugg;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for floating-point expressions that
+ /// can be expressed using built-in methods to improve accuracy
+ /// at the cost of performance.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts accuracy.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = 3f32;
+ /// let _ = a.powf(1.0 / 3.0);
+ /// let _ = (1.0 + a).ln();
+ /// let _ = a.exp() - 1.0;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a = 3f32;
+ /// let _ = a.cbrt();
+ /// let _ = a.ln_1p();
+ /// let _ = a.exp_m1();
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub IMPRECISE_FLOPS,
+ nursery,
+ "usage of imprecise floating point operations"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for floating-point expressions that
+ /// can be expressed using built-in methods to improve both
+ /// accuracy and performance.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts accuracy and performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::f32::consts::E;
+ ///
+ /// let a = 3f32;
+ /// let _ = (2f32).powf(a);
+ /// let _ = E.powf(a);
+ /// let _ = a.powf(1.0 / 2.0);
+ /// let _ = a.log(2.0);
+ /// let _ = a.log(10.0);
+ /// let _ = a.log(E);
+ /// let _ = a.powf(2.0);
+ /// let _ = a * 2.0 + 4.0;
+ /// let _ = if a < 0.0 {
+ /// -a
+ /// } else {
+ /// a
+ /// };
+ /// let _ = if a < 0.0 {
+ /// a
+ /// } else {
+ /// -a
+ /// };
+ /// ```
+ ///
+ /// is better expressed as
+ ///
+ /// ```rust
+ /// use std::f32::consts::E;
+ ///
+ /// let a = 3f32;
+ /// let _ = a.exp2();
+ /// let _ = a.exp();
+ /// let _ = a.sqrt();
+ /// let _ = a.log2();
+ /// let _ = a.log10();
+ /// let _ = a.ln();
+ /// let _ = a.powi(2);
+ /// let _ = a.mul_add(2.0, 4.0);
+ /// let _ = a.abs();
+ /// let _ = -a.abs();
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub SUBOPTIMAL_FLOPS,
+ nursery,
+ "usage of sub-optimal floating point operations"
+}
+
+declare_lint_pass!(FloatingPointArithmetic => [
+ IMPRECISE_FLOPS,
+ SUBOPTIMAL_FLOPS
+]);
+
+// Returns the specialized log method for a given base if base is constant
+// and is one of 2, 10 and e
+fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), base) {
+ if F32(2.0) == value || F64(2.0) == value {
+ return Some("log2");
+ } else if F32(10.0) == value || F64(10.0) == value {
+ return Some("log10");
+ } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ return Some("ln");
+ }
+ }
+
+ None
+}
+
+// Adds type suffixes and parenthesis to method receivers if necessary
+fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> {
+ let mut suggestion = Sugg::hir(cx, expr, "..");
+
+ if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
+ expr = inner_expr;
+ }
+
+ if_chain! {
+ // if the expression is a float literal and it is unsuffixed then
+ // add a suffix so the suggestion is valid and unambiguous
+ if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind();
+ if let ExprKind::Lit(lit) = &expr.kind;
+ if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
+ then {
+ let op = format!(
- format!("{}.{}()", Sugg::hir(cx, receiver, "..").maybe_par(), method),
++ "{suggestion}{}{}",
+ // Check for float literals without numbers following the decimal
+ // separator such as `2.` and adds a trailing zero
+ if sym.as_str().ends_with('.') {
+ "0"
+ } else {
+ ""
+ },
+ float_ty.name_str()
+ ).into();
+
+ suggestion = match suggestion {
+ Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
+ _ => Sugg::NonParen(op)
+ };
+ }
+ }
+
+ suggestion.maybe_par()
+}
+
+fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some(method) = get_specialized_log_method(cx, &args[0]) {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "logarithm for bases 2, 10 and e can be computed more accurately",
+ "consider using",
- format!("{}.{}()", prepare_receiver_sugg(cx, &args[0]), method),
++ format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_par()),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
+// suggest usage of `(x + (y - 1)).ln_1p()` instead
+fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = receiver.kind
+ {
+ let recv = match (
+ constant(cx, cx.typeck_results(), lhs),
+ constant(cx, cx.typeck_results(), rhs),
+ ) {
+ (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs,
+ (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs,
+ _ => return,
+ };
+
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "ln(1 + x) can be computed more accurately",
+ "consider using",
+ format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// Returns an integer if the float constant is a whole number and it can be
+// converted to an integer without loss of precision. For now we only check
+// ranges [-16777215, 16777216) for type f32 as whole number floats outside
+// this range are lossy and ambiguous.
+#[expect(clippy::cast_possible_truncation)]
+fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
+ match value {
+ F32(num) if num.fract() == 0.0 => {
+ if (-16_777_215.0..16_777_216.0).contains(num) {
+ Some(num.round() as i32)
+ } else {
+ None
+ }
+ },
+ F64(num) if num.fract() == 0.0 => {
+ if (-2_147_483_648.0..2_147_483_648.0).contains(num) {
+ Some(num.round() as i32)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
+ // Check receiver
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), receiver) {
+ if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ Some("exp")
+ } else if F32(2.0) == value || F64(2.0) == value {
+ Some("exp2")
+ } else {
+ None
+ } {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "exponent for bases 2 and e can be computed more accurately",
+ "consider using",
- node: BinOpKind::Add, ..
++ format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ // Check argument
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
+ let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
+ (
+ SUBOPTIMAL_FLOPS,
+ "square-root of a number can be computed more efficiently and accurately",
+ format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
+ )
+ } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
+ (
+ IMPRECISE_FLOPS,
+ "cube-root of a number can be computed more accurately",
+ format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
+ )
+ } else if let Some(exponent) = get_integer_from_float_constant(&value) {
+ (
+ SUBOPTIMAL_FLOPS,
+ "exponentiation with integer powers can be computed more efficiently",
+ format!(
+ "{}.powi({})",
+ Sugg::hir(cx, receiver, "..").maybe_par(),
+ numeric_literal::format(&exponent.to_string(), None, false)
+ ),
+ )
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ help,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
+ if value == Int(2) {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let Some(grandparent) = get_parent_expr(cx, parent) {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
+ {
+ if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
+ return;
+ }
+ }
+ }
+
+ if let ExprKind::Binary(
+ Spanned {
- Sugg::hir(cx, receiver, ".."),
- Sugg::hir(cx, other_addend, ".."),
++ node: op @ (BinOpKind::Add | BinOpKind::Sub),
++ ..
+ },
+ lhs,
+ rhs,
+ ) = parent.kind
+ {
+ let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
+
++ // Negate expr if original code has subtraction and expr is on the right side
++ let maybe_neg_sugg = |expr, hir_id| {
++ let sugg = Sugg::hir(cx, expr, "..");
++ if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
++ format!("-{sugg}")
++ } else {
++ sugg.to_string()
++ }
++ };
++
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ parent.span,
+ "multiply and add expressions can be calculated more efficiently and accurately",
+ "consider using",
+ format!(
+ "{}.mul_add({}, {})",
+ Sugg::hir(cx, receiver, "..").maybe_par(),
- node: BinOpKind::Add, ..
++ maybe_neg_sugg(receiver, expr.hir_id),
++ maybe_neg_sugg(other_addend, other_addend.hir_id),
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ add_lhs,
+ add_rhs,
+ ) = receiver.kind
+ {
+ // check if expression of the form x * x + y * y
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
+ if eq_expr_value(cx, lmul_lhs, lmul_rhs);
+ if eq_expr_value(cx, rmul_lhs, rmul_rhs);
+ then {
+ return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, "..").maybe_par(), Sugg::hir(cx, rmul_lhs, "..")));
+ }
+ }
+
+ // check if expression of the form x.powi(2) + y.powi(2)
+ if_chain! {
+ if let ExprKind::MethodCall(
+ PathSegment { ident: lmethod_name, .. },
+ largs_0, [largs_1, ..],
+ _
+ ) = &add_lhs.kind;
+ if let ExprKind::MethodCall(
+ PathSegment { ident: rmethod_name, .. },
+ rargs_0, [rargs_1, ..],
+ _
+ ) = &add_rhs.kind;
+ if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
+ if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
+ if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
+ if Int(2) == lvalue && Int(2) == rvalue;
+ then {
+ return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
+ }
+ }
+ }
+
+ None
+}
+
+fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
+ if let Some(message) = detect_hypot(cx, receiver) {
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "hypotenuse can be computed more accurately",
+ "consider using",
+ message,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `x.exp() - y` where y > 1
+// and suggest usage of `x.exp_m1() - (y - 1)` instead
+fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
+ if cx.typeck_results().expr_ty(lhs).is_floating_point();
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
+ if F32(1.0) == value || F64(1.0) == value;
+ if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind;
+ if cx.typeck_results().expr_ty(self_arg).is_floating_point();
+ if path.ident.name.as_str() == "exp";
+ then {
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "(e.pow(x) - 1) can be computed more accurately",
+ "consider using",
+ format!(
+ "{}.exp_m1()",
+ Sugg::hir(cx, self_arg, "..").maybe_par()
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
+ if cx.typeck_results().expr_ty(lhs).is_floating_point();
+ if cx.typeck_results().expr_ty(rhs).is_floating_point();
+ then {
+ return Some((lhs, rhs));
+ }
+ }
+
+ None
+}
+
+// TODO: Fix rust-lang/rust-clippy#4735
+fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Binary(
+ Spanned {
- (inner_lhs, inner_rhs, rhs)
++ node: op @ (BinOpKind::Add | BinOpKind::Sub),
++ ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind
+ {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind {
+ if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
+ return;
+ }
+ }
+ }
+
++ let maybe_neg_sugg = |expr| {
++ let sugg = Sugg::hir(cx, expr, "..");
++ if let BinOpKind::Sub = op {
++ format!("-{sugg}")
++ } else {
++ sugg.to_string()
++ }
++ };
++
+ let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) {
- (inner_lhs, inner_rhs, lhs)
++ (
++ inner_lhs,
++ Sugg::hir(cx, inner_rhs, "..").to_string(),
++ maybe_neg_sugg(rhs),
++ )
+ } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) {
- format!(
- "{}.mul_add({}, {})",
- prepare_receiver_sugg(cx, recv),
- Sugg::hir(cx, arg1, ".."),
- Sugg::hir(cx, arg2, ".."),
- ),
++ (
++ inner_lhs,
++ maybe_neg_sugg(inner_rhs),
++ Sugg::hir(cx, lhs, "..").to_string(),
++ )
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "multiply and add expressions can be calculated more efficiently and accurately",
+ "consider using",
++ format!("{}.mul_add({arg1}, {arg2})", prepare_receiver_sugg(cx, recv)),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+/// Returns true iff expr is an expression which tests whether or not
+/// test is positive or an expression which tests whether or not test
+/// is nonnegative.
+/// Used for check-custom-abs function below
+fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// See [`is_testing_positive`]
+fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// Returns true iff expr is some zero literal
+fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match constant_simple(cx, cx.typeck_results(), expr) {
+ Some(Constant::Int(i)) => i == 0,
+ Some(Constant::F32(f)) => f == 0.0,
+ Some(Constant::F64(f)) => f == 0.0,
+ _ => false,
+ }
+}
+
+/// If the two expressions are negations of each other, then it returns
+/// a tuple, in which the first element is true iff expr1 is the
+/// positive expressions, and the second element is the positive
+/// one of the two expressions
+/// If the two expressions are not negations of each other, then it
+/// returns None.
+fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
+ if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind {
+ if eq_expr_value(cx, expr1_negated, expr2) {
+ return Some((false, expr2));
+ }
+ }
+ if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind {
+ if eq_expr_value(cx, expr1, expr2_negated) {
+ return Some((true, expr1));
+ }
+ }
+ None
+}
+
+fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr);
+ let if_body_expr = peel_blocks(then);
+ let else_body_expr = peel_blocks(r#else);
+ if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
+ then {
+ let positive_abs_sugg = (
+ "manual implementation of `abs` method",
+ format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
+ );
+ let negative_abs_sugg = (
+ "manual implementation of negation of `abs` method",
+ format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
+ );
+ let sugg = if is_testing_positive(cx, cond, body) {
+ if if_expr_positive {
+ positive_abs_sugg
+ } else {
+ negative_abs_sugg
+ }
+ } else if is_testing_negative(cx, cond, body) {
+ if if_expr_positive {
+ negative_abs_sugg
+ } else {
+ positive_abs_sugg
+ }
+ } else {
+ return;
+ };
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ sugg.0,
+ "try",
+ sugg.1,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
+ if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
+ then {
+ return method_name_a.as_str() == method_name_b.as_str() &&
+ args_a.len() == args_b.len() &&
+ (
+ ["ln", "log2", "log10"].contains(&method_name_a.as_str()) ||
+ method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])
+ );
+ }
+ }
+
+ false
+}
+
+fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // check if expression of the form x.logN() / y.logN()
+ if_chain! {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Div, ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind;
+ if are_same_base_logs(cx, lhs, rhs);
+ if let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind;
+ if let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind;
+ then {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "log base can be expressed more clearly",
+ "consider using",
+ format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Div, ..
+ },
+ div_lhs,
+ div_rhs,
+ ) = &expr.kind;
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Mul, ..
+ },
+ mul_lhs,
+ mul_rhs,
+ ) = &div_lhs.kind;
+ if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs);
+ if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs);
+ then {
+ // TODO: also check for constant values near PI/180 or 180/PI
+ if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
+ (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
+ {
+ let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
+ if_chain! {
+ if let ExprKind::Lit(ref literal) = mul_lhs.kind;
+ if let ast::LitKind::Float(ref value, float_type) = literal.node;
+ if float_type == ast::LitFloatType::Unsuffixed;
+ then {
+ if value.as_str().ends_with('.') {
+ proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
+ } else {
+ proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
+ }
+ }
+ }
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "conversion to degrees can be done more accurately",
+ "consider using",
+ proposal,
+ Applicability::MachineApplicable,
+ );
+ } else if
+ (F32(180_f32) == rvalue || F64(180_f64) == rvalue) &&
+ (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
+ {
+ let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
+ if_chain! {
+ if let ExprKind::Lit(ref literal) = mul_lhs.kind;
+ if let ast::LitKind::Float(ref value, float_type) = literal.node;
+ if float_type == ast::LitFloatType::Unsuffixed;
+ then {
+ if value.as_str().ends_with('.') {
+ proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
+ } else {
+ proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
+ }
+ }
+ }
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "conversion to radians can be done more accurately",
+ "consider using",
+ proposal,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // All of these operations are currently not const.
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ if let ExprKind::MethodCall(path, receiver, args, _) = &expr.kind {
+ let recv_ty = cx.typeck_results().expr_ty(receiver);
+
+ if recv_ty.is_floating_point() {
+ match path.ident.name.as_str() {
+ "ln" => check_ln1p(cx, expr, receiver),
+ "log" => check_log_base(cx, expr, receiver, args),
+ "powf" => check_powf(cx, expr, receiver, args),
+ "powi" => check_powi(cx, expr, receiver, args),
+ "sqrt" => check_hypot(cx, expr, receiver),
+ _ => {},
+ }
+ }
+ } else {
+ check_expm1(cx, expr);
+ check_mul_add(cx, expr);
+ check_custom_abs(cx, expr);
+ check_log_division(cx, expr);
+ check_radians(cx, expr);
+ }
+ }
+}
--- /dev/null
- let sugg = format!("{}.to_string()", s_expand);
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::Sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `format!("string literal with no
+ /// argument")` and `format!("{}", foo)` where `foo` is a string.
+ ///
+ /// ### Why is this bad?
+ /// There is no point of doing that. `format!("foo")` can
+ /// be replaced by `"foo".to_owned()` if you really need a `String`. The even
+ /// worse `&format!("foo")` is often encountered in the wild. `format!("{}",
+ /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()`
+ /// if `foo: &str`.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// let foo = "foo";
+ /// format!("{}", foo);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let foo = "foo";
+ /// foo.to_owned();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_FORMAT,
+ complexity,
+ "useless use of `format!`"
+}
+
+declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
+
+impl<'tcx> LateLintPass<'tcx> for UselessFormat {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (format_args, call_site) = if_chain! {
+ if let Some(macro_call) = root_macro_call_first_node(cx, expr);
+ if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id);
+ if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn);
+ then {
+ (format_args, macro_call.span)
+ } else {
+ return
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ if format_args.args.is_empty() {
+ match *format_args.format_string.parts {
+ [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
+ [_] => {
+ // Simulate macro expansion, converting {{ and }} to { and }.
+ let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}");
++ let sugg = format!("{s_expand}.to_string()");
+ span_useless_format(cx, call_site, sugg, applicability);
+ },
+ [..] => {},
+ }
+ } else if let [arg] = &*format_args.args {
+ let value = arg.param.value;
+ if_chain! {
+ if format_args.format_string.parts == [kw::Empty];
+ if arg.format.is_default();
+ if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()),
+ ty::Str => true,
+ _ => false,
+ };
+ then {
+ let is_new_string = match value.kind {
+ ExprKind::Binary(..) => true,
+ ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
+ _ => false,
+ };
+ let sugg = if is_new_string {
+ snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned()
+ } else {
+ let sugg = Sugg::hir_with_applicability(cx, value, "<arg>", &mut applicability);
+ format!("{}.to_string()", sugg.maybe_par())
+ };
+ span_useless_format(cx, call_site, sugg, applicability);
+ }
+ }
+ };
+ }
+}
+
+fn span_useless_format_empty(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) {
+ span_lint_and_sugg(
+ cx,
+ USELESS_FORMAT,
+ span,
+ "useless use of `format!`",
+ "consider using `String::new()`",
+ sugg,
+ applicability,
+ );
+}
+
+fn span_useless_format(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) {
+ span_lint_and_sugg(
+ cx,
+ USELESS_FORMAT,
+ span,
+ "useless use of `format!`",
+ "consider using `.to_string()`",
+ sugg,
+ applicability,
+ );
+}
--- /dev/null
- use clippy_utils::is_diag_trait_item;
- use clippy_utils::macros::{is_format_macro, FormatArgsExpn};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
- use rustc_hir::{Expr, ExprKind, HirId};
++use clippy_utils::macros::FormatParamKind::{Implicit, Named, Numbered, Starred};
++use clippy_utils::macros::{is_format_macro, FormatArgsExpn, FormatParam, FormatParamUsage};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
++use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs};
+use if_chain::if_chain;
+use itertools::Itertools;
+use rustc_errors::Applicability;
- use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_hir::{Expr, ExprKind, HirId, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment};
+use rustc_middle::ty::Ty;
- declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `format!` within the arguments of another macro that does
+ /// formatting such as `format!` itself, `write!` or `println!`. Suggests
+ /// inlining the `format!` call.
+ ///
+ /// ### Why is this bad?
+ /// The recommended code is both shorter and avoids a temporary allocation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: {}", format!("something failed at {}", Location::caller()));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller());
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub FORMAT_IN_FORMAT_ARGS,
+ perf,
+ "`format!` used in a macro that does formatting"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
+ /// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
+ /// in a macro that does formatting.
+ ///
+ /// ### Why is this bad?
+ /// Since the type implements `Display`, the use of `to_string` is
+ /// unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller().to_string());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller());
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub TO_STRING_IN_FORMAT_ARGS,
+ perf,
+ "`to_string` applied to a type that implements `Display` in format args"
+}
+
- &format!("`format!` in `{}!` args", name),
++declare_clippy_lint! {
++ /// ### What it does
++ /// Detect when a variable is not inlined in a format string,
++ /// and suggests to inline it.
++ ///
++ /// ### Why is this bad?
++ /// Non-inlined code is slightly more difficult to read and understand,
++ /// as it requires arguments to be matched against the format string.
++ /// The inlined syntax, where allowed, is simpler.
++ ///
++ /// ### Example
++ /// ```rust
++ /// # let var = 42;
++ /// # let width = 1;
++ /// # let prec = 2;
++ /// format!("{}", var);
++ /// format!("{v:?}", v = var);
++ /// format!("{0} {0}", var);
++ /// format!("{0:1$}", var, width);
++ /// format!("{:.*}", prec, var);
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// # let var = 42;
++ /// # let width = 1;
++ /// # let prec = 2;
++ /// format!("{var}");
++ /// format!("{var:?}");
++ /// format!("{var} {var}");
++ /// format!("{var:width$}");
++ /// format!("{var:.prec$}");
++ /// ```
++ ///
++ /// ### Known Problems
++ ///
++ /// There may be a false positive if the format string is expanded from certain proc macros:
++ ///
++ /// ```ignore
++ /// println!(indoc!("{}"), var);
++ /// ```
++ ///
++ /// If a format string contains a numbered argument that cannot be inlined
++ /// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
++ #[clippy::version = "1.65.0"]
++ pub UNINLINED_FORMAT_ARGS,
++ pedantic,
++ "using non-inlined variables in `format!` calls"
++}
++
++impl_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, UNINLINED_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
++
++pub struct FormatArgs {
++ msrv: Option<RustcVersion>,
++}
++
++impl FormatArgs {
++ #[must_use]
++ pub fn new(msrv: Option<RustcVersion>) -> Self {
++ Self { msrv }
++ }
++}
+
+impl<'tcx> LateLintPass<'tcx> for FormatArgs {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if let Some(format_args) = FormatArgsExpn::parse(cx, expr);
+ let expr_expn_data = expr.span.ctxt().outer_expn_data();
+ let outermost_expn_data = outermost_expn_data(expr_expn_data);
+ if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
+ if is_format_macro(cx, macro_def_id);
+ if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
+ then {
+ for arg in &format_args.args {
+ if !arg.format.is_default() {
+ continue;
+ }
+ if is_aliased(&format_args, arg.param.value.hir_id) {
+ continue;
+ }
+ check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
+ check_to_string_in_format_args(cx, name, arg.param.value);
+ }
++ if meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
++ check_uninlined_args(cx, &format_args, outermost_expn_data.call_site);
++ }
+ }
+ }
+ }
++
++ extract_msrv_attr!(LateContext);
++}
++
++fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_site: Span) {
++ if args.format_string.span.from_expansion() {
++ return;
++ }
++
++ let mut fixes = Vec::new();
++ // If any of the arguments are referenced by an index number,
++ // and that argument is not a simple variable and cannot be inlined,
++ // we cannot remove any other arguments in the format string,
++ // because the index numbers might be wrong after inlining.
++ // Example of an un-inlinable format: print!("{}{1}", foo, 2)
++ if !args.params().all(|p| check_one_arg(args, &p, &mut fixes)) || fixes.is_empty() {
++ return;
++ }
++
++ // FIXME: Properly ignore a rare case where the format string is wrapped in a macro.
++ // Example: `format!(indoc!("{}"), foo);`
++ // If inlined, they will cause a compilation error:
++ // > to avoid ambiguity, `format_args!` cannot capture variables
++ // > when the format string is expanded from a macro
++ // @Alexendoo explanation:
++ // > indoc! is a proc macro that is producing a string literal with its span
++ // > set to its input it's not marked as from expansion, and since it's compatible
++ // > tokenization wise clippy_utils::is_from_proc_macro wouldn't catch it either
++ // This might be a relatively expensive test, so do it only we are ready to replace.
++ // See more examples in tests/ui/uninlined_format_args.rs
++
++ span_lint_and_then(
++ cx,
++ UNINLINED_FORMAT_ARGS,
++ call_site,
++ "variables can be used directly in the `format!` string",
++ |diag| {
++ diag.multipart_suggestion("change this to", fixes, Applicability::MachineApplicable);
++ },
++ );
++}
++
++fn check_one_arg(args: &FormatArgsExpn<'_>, param: &FormatParam<'_>, fixes: &mut Vec<(Span, String)>) -> bool {
++ if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
++ && let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
++ && let [segment] = path.segments
++ && let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id)
++ {
++ let replacement = match param.usage {
++ FormatParamUsage::Argument => segment.ident.name.to_string(),
++ FormatParamUsage::Width => format!("{}$", segment.ident.name),
++ FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
++ };
++ fixes.push((param.span, replacement));
++ fixes.push((arg_span, String::new()));
++ true // successful inlining, continue checking
++ } else {
++ // if we can't inline a numbered argument, we can't continue
++ param.kind != Numbered
++ }
+}
+
+fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
+ if expn_data.call_site.from_expansion() {
+ outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
+ } else {
+ expn_data
+ }
+}
+
+fn check_format_in_format_args(
+ cx: &LateContext<'_>,
+ call_site: Span,
+ name: Symbol,
+ arg: &Expr<'_>,
+) {
+ let expn_data = arg.span.ctxt().outer_expn_data();
+ if expn_data.call_site.from_expansion() {
+ return;
+ }
+ let Some(mac_id) = expn_data.macro_def_id else { return };
+ if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
+ return;
+ }
+ span_lint_and_then(
+ cx,
+ FORMAT_IN_FORMAT_ARGS,
+ call_site,
- "combine the `format!(..)` arguments with the outer `{}!(..)` call",
- name
++ &format!("`format!` in `{name}!` args"),
+ |diag| {
+ diag.help(&format!(
- "`to_string` applied to a type that implements `Display` in `{}!` args",
- name
++ "combine the `format!(..)` arguments with the outer `{name}!(..)` call"
+ ));
+ diag.help("or consider changing `format!` to `format_args!`");
+ },
+ );
+}
+
+fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
+ if_chain! {
+ if !value.span.from_expansion();
+ if let ExprKind::MethodCall(_, receiver, [], _) = value.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
+ if is_diag_trait_item(cx, method_def_id, sym::ToString);
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
+ let (n_needed_derefs, target) =
+ count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter());
+ if implements_trait(cx, target, display_trait_id, &[]);
+ if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait();
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
+ if n_needed_derefs == 0 && !needs_ref {
+ span_lint_and_sugg(
+ cx,
+ TO_STRING_IN_FORMAT_ARGS,
+ value.span.with_lo(receiver.span.hi()),
+ &format!(
- "`to_string` applied to a type that implements `Display` in `{}!` args",
- name
++ "`to_string` applied to a type that implements `Display` in `{name}!` args"
+ ),
+ "remove this",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ TO_STRING_IN_FORMAT_ARGS,
+ value.span,
+ &format!(
- "{}{:*>width$}{}",
++ "`to_string` applied to a type that implements `Display` in `{name}!` args"
+ ),
+ "use this",
+ format!(
- "",
- receiver_snippet,
- width = n_needed_derefs
++ "{}{:*>n_needed_derefs$}{receiver_snippet}",
+ if needs_ref { "&" } else { "" },
- // Returns true if `hir_id` is referred to by multiple format params
++ ""
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
++/// Returns true if `hir_id` is referred to by multiple format params
+fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
+ args.params().filter(|param| param.value.hir_id == hir_id).at_most_one().is_err()
+}
+
+fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
+where
+ I: Iterator<Item = &'tcx Adjustment<'tcx>>,
+{
+ let mut n_total = 0;
+ let mut n_needed = 0;
+ loop {
+ if let Some(Adjustment { kind: Adjust::Deref(overloaded_deref), target }) = iter.next() {
+ n_total += 1;
+ if overloaded_deref.is_some() {
+ n_needed = n_total;
+ }
+ ty = *target;
+ } else {
+ return (n_needed, ty);
+ }
+ }
+}
--- /dev/null
- &format!("use of `{}!` in `{}` impl", name, impl_trait.name),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
+use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, symbol::kw, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
+ /// which uses `self` as a parameter.
+ /// This is typically done indirectly with the `write!` macro or with `to_string()`.
+ ///
+ /// ### Why is this bad?
+ /// This will lead to infinite recursion and a stack overflow.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// struct Structure(i32);
+ /// impl fmt::Display for Structure {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "{}", self.to_string())
+ /// }
+ /// }
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// struct Structure(i32);
+ /// impl fmt::Display for Structure {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "{}", self.0)
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub RECURSIVE_FORMAT_IMPL,
+ correctness,
+ "Format trait method called while implementing the same Format trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
+ /// implementation of a formatting trait.
+ ///
+ /// ### Why is this bad?
+ /// Using a print macro is likely unintentional since formatting traits
+ /// should write to the `Formatter`, not stdout/stderr.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::fmt::{Display, Error, Formatter};
+ ///
+ /// struct S;
+ /// impl Display for S {
+ /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ /// println!("S");
+ ///
+ /// Ok(())
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::fmt::{Display, Error, Formatter};
+ ///
+ /// struct S;
+ /// impl Display for S {
+ /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ /// writeln!(f, "S");
+ ///
+ /// Ok(())
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub PRINT_IN_FORMAT_IMPL,
+ suspicious,
+ "use of a print macro in a formatting trait impl"
+}
+
+#[derive(Clone, Copy)]
+struct FormatTrait {
+ /// e.g. `sym::Display`
+ name: Symbol,
+ /// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
+ formatter_name: Option<Symbol>,
+}
+
+#[derive(Default)]
+pub struct FormatImpl {
+ // Whether we are inside Display or Debug trait impl - None for neither
+ format_trait_impl: Option<FormatTrait>,
+}
+
+impl FormatImpl {
+ pub fn new() -> Self {
+ Self {
+ format_trait_impl: None,
+ }
+ }
+}
+
+impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);
+
+impl<'tcx> LateLintPass<'tcx> for FormatImpl {
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
+ self.format_trait_impl = is_format_trait_impl(cx, impl_item);
+ }
+
+ fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
+ // Assume no nested Impl of Debug and Display within eachother
+ if is_format_trait_impl(cx, impl_item).is_some() {
+ self.format_trait_impl = None;
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let Some(format_trait_impl) = self.format_trait_impl else { return };
+
+ if format_trait_impl.name == sym::Display {
+ check_to_string_in_display(cx, expr);
+ }
+
+ check_self_in_format_args(cx, expr, format_trait_impl);
+ check_print_in_format_impl(cx, expr, format_trait_impl);
+ }
+}
+
+fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ // Get the hir_id of the object we are calling the method on
+ if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind;
+ // Is the method to_string() ?
+ if path.ident.name == sym::to_string;
+ // Is the method a part of the ToString trait? (i.e. not to_string() implemented
+ // separately)
+ if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if is_diag_trait_item(cx, expr_def_id, sym::ToString);
+ // Is the method is called on self
+ if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
+ if let [segment] = path.segments;
+ if segment.ident.name == kw::SelfLower;
+ then {
+ span_lint(
+ cx,
+ RECURSIVE_FORMAT_IMPL,
+ expr.span,
+ "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
+ );
+ }
+ }
+}
+
+fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
+ // Check each arg in format calls - do we ever use Display on self (directly or via deref)?
+ if_chain! {
+ if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
+ if let macro_def_id = outer_macro.def_id;
+ if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
+ if is_format_macro(cx, macro_def_id);
+ then {
+ for arg in format_args.args {
+ if arg.format.r#trait != impl_trait.name {
+ continue;
+ }
+ check_format_arg_self(cx, expr, &arg, impl_trait);
+ }
+ }
+ }
+}
+
+fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
+ // Handle multiple dereferencing of references e.g. &&self
+ // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
+ // Since the argument to fmt is itself a reference: &self
+ let reference = peel_ref_operators(cx, arg.param.value);
+ let map = cx.tcx.hir();
+ // Is the reference self?
+ if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
+ let FormatTrait { name, .. } = impl_trait;
+ span_lint(
+ cx,
+ RECURSIVE_FORMAT_IMPL,
+ expr.span,
+ &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
+ );
+ }
+}
+
+fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
+ if_chain! {
+ if let Some(macro_call) = root_macro_call_first_node(cx, expr);
+ if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
+ then {
+ let replacement = match name {
+ sym::print_macro | sym::eprint_macro => "write",
+ sym::println_macro | sym::eprintln_macro => "writeln",
+ _ => return,
+ };
+
+ let name = name.as_str().strip_suffix("_macro").unwrap();
+
+ span_lint_and_sugg(
+ cx,
+ PRINT_IN_FORMAT_IMPL,
+ macro_call.span,
- format!("{}!({}, ..)", replacement, formatter_name)
++ &format!("use of `{name}!` in `{}` impl", impl_trait.name),
+ "replace with",
+ if let Some(formatter_name) = impl_trait.formatter_name {
- format!("{}!(..)", replacement)
++ format!("{replacement}!({formatter_name}, ..)")
+ } else {
++ format!("{replacement}!(..)")
+ },
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+}
+
+fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
+ if_chain! {
+ if impl_item.ident.name == sym::fmt;
+ if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
+ if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
+ if let Some(did) = trait_ref.trait_def_id();
+ if let Some(name) = cx.tcx.get_diagnostic_name(did);
+ if matches!(name, sym::Debug | sym::Display);
+ then {
+ let body = cx.tcx.hir().body(body_id);
+ let formatter_name = body.params.get(1)
+ .and_then(|param| param.pat.simple_ident())
+ .map(|ident| ident.name);
+
+ Some(FormatTrait {
+ name,
+ formatter_name,
+ })
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- really are doing `.. = ({op} ..)`",
- op = op
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
+use clippy_utils::is_span_if;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
+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::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of the non-existent `=*`, `=!` and `=-`
+ /// operators.
+ ///
+ /// ### Why is this bad?
+ /// This is either a typo of `*=`, `!=` or `-=` or
+ /// confusing.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// a =- 42; // confusing, should it be `a -= 42` or `a = -42`?
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ suspicious,
+ "suspicious formatting of `*=`, `-=` or `!=`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the formatting of a unary operator on the right hand side
+ /// of a binary operator. It lints if there is no space between the binary and unary operators,
+ /// but there is a space between the unary and its operand.
+ ///
+ /// ### Why is this bad?
+ /// This is either a typo in the binary operator or confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = true;
+ /// # let bar = false;
+ /// // &&! looks like a different operator
+ /// if foo &&! bar {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = true;
+ /// # let bar = false;
+ /// if foo && !bar {}
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub SUSPICIOUS_UNARY_OP_FORMATTING,
+ suspicious,
+ "suspicious formatting of unary `-` or `!` on the RHS of a BinOp"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for formatting of `else`. It lints if the `else`
+ /// is followed immediately by a newline or the `else` seems to be missing.
+ ///
+ /// ### Why is this bad?
+ /// This is probably some refactoring remnant, even if the
+ /// code is correct, it might look confusing.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// if foo {
+ /// } { // looks like an `else` is missing here
+ /// }
+ ///
+ /// if foo {
+ /// } if bar { // looks like an `else` is missing here
+ /// }
+ ///
+ /// if foo {
+ /// } else
+ ///
+ /// { // this is the `else` block of the previous `if`, but should it be?
+ /// }
+ ///
+ /// if foo {
+ /// } else
+ ///
+ /// if bar { // this is the `else` block of the previous `if`, but should it be?
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_ELSE_FORMATTING,
+ suspicious,
+ "suspicious formatting of `else`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for possible missing comma in an array. It lints if
+ /// an array element is a binary operator expression and it lies on two lines.
+ ///
+ /// ### Why is this bad?
+ /// This could lead to unexpected results.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let a = &[
+ /// -1, -2, -3 // <= no comma here
+ /// -4, -5, -6
+ /// ];
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub POSSIBLE_MISSING_COMMA,
+ correctness,
+ "possible missing comma in array"
+}
+
+declare_lint_pass!(Formatting => [
+ SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ SUSPICIOUS_UNARY_OP_FORMATTING,
+ SUSPICIOUS_ELSE_FORMATTING,
+ POSSIBLE_MISSING_COMMA
+]);
+
+impl EarlyLintPass for Formatting {
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
+ for w in block.stmts.windows(2) {
+ if let (StmtKind::Expr(first), StmtKind::Expr(second) | StmtKind::Semi(second)) = (&w[0].kind, &w[1].kind) {
+ check_missing_else(cx, first, second);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ check_assign(cx, expr);
+ check_unop(cx, expr);
+ check_else(cx, expr);
+ check_array(cx, expr);
+ }
+}
+
+/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
+fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
+ if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
+ if !lhs.span.from_expansion() && !rhs.span.from_expansion() {
+ let eq_span = lhs.span.between(rhs.span);
+ if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
+ if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
+ let op = UnOp::to_string(op);
+ let eqop_span = lhs.span.between(sub_rhs.span);
+ if eq_snippet.ends_with('=') {
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ eqop_span,
+ &format!(
+ "this looks like you are trying to use `.. {op}= ..`, but you \
- &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op),
++ really are doing `.. = ({op} ..)`"
+ ),
+ None,
- "by not having a space between `{binop}` and `{unop}` it looks like \
- `{binop}{unop}` is a single operator",
- binop = binop_str,
- unop = unop_str
++ &format!("to remove this lint, use either `{op}=` or `= {op}`"),
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint.
+fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
+ if_chain! {
+ if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
+ if !lhs.span.from_expansion() && !rhs.span.from_expansion();
+ // span between BinOp LHS and RHS
+ let binop_span = lhs.span.between(rhs.span);
+ // if RHS is an UnOp
+ if let ExprKind::Unary(op, ref un_rhs) = rhs.kind;
+ // from UnOp operator to UnOp operand
+ let unop_operand_span = rhs.span.until(un_rhs.span);
+ if let Some(binop_snippet) = snippet_opt(cx, binop_span);
+ if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span);
+ let binop_str = BinOpKind::to_string(&binop.node);
+ // no space after BinOp operator and space after UnOp operator
+ if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ');
+ then {
+ let unop_str = UnOp::to_string(op);
+ let eqop_span = lhs.span.between(un_rhs.span);
+ span_lint_and_help(
+ cx,
+ SUSPICIOUS_UNARY_OP_FORMATTING,
+ eqop_span,
+ &format!(
- "put a space between `{binop}` and `{unop}` and remove the space after `{unop}`",
- binop = binop_str,
- unop = unop_str
++ "by not having a space between `{binop_str}` and `{unop_str}` it looks like \
++ `{binop_str}{unop_str}` is a single operator"
+ ),
+ None,
+ &format!(
- &format!("this is an `else {}` but the formatting might hide it", else_desc),
++ "put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"
+ ),
+ );
+ }
+ }
+}
+
+/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
+fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
+ if_chain! {
+ if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
+ if is_block(else_) || is_if(else_);
+ if !then.span.from_expansion() && !else_.span.from_expansion();
+ if !in_external_macro(cx.sess(), expr.span);
+
+ // workaround for rust-lang/rust#43081
+ if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
+
+ // this will be a span from the closing ‘}’ of the “then” block (excluding) to
+ // the “if” of the “else if” block (excluding)
+ let else_span = then.span.between(else_.span);
+
+ // the snippet should look like " else \n " with maybe comments anywhere
+ // it’s bad when there is a ‘\n’ after the “else”
+ if let Some(else_snippet) = snippet_opt(cx, else_span);
+ if let Some((pre_else, post_else)) = else_snippet.split_once("else");
+ if let Some((_, post_else_post_eol)) = post_else.split_once('\n');
+
+ then {
+ // Allow allman style braces `} \n else \n {`
+ if_chain! {
+ if is_block(else_);
+ if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n');
+ // Exactly one eol before and after the else
+ if !pre_else_post_eol.contains('\n');
+ if !post_else_post_eol.contains('\n');
+ then {
+ return;
+ }
+ }
+
+ let else_desc = if is_if(else_) { "if" } else { "{..}" };
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_ELSE_FORMATTING,
+ else_span,
- `else` and `{}`",
- else_desc,
++ &format!("this is an `else {else_desc}` but the formatting might hide it"),
+ None,
+ &format!(
+ "to remove this lint, remove the `else` or remove the new line between \
- &format!("this looks like {} but the `else` is missing", looks_like),
++ `else` and `{else_desc}`",
+ ),
+ );
+ }
+ }
+}
+
+#[must_use]
+fn has_unary_equivalent(bin_op: BinOpKind) -> bool {
+ // &, *, -
+ bin_op == BinOpKind::And || bin_op == BinOpKind::Mul || bin_op == BinOpKind::Sub
+}
+
+fn indentation(cx: &EarlyContext<'_>, span: Span) -> usize {
+ cx.sess().source_map().lookup_char_pos(span.lo()).col.0
+}
+
+/// Implementation of the `POSSIBLE_MISSING_COMMA` lint for array
+fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
+ if let ExprKind::Array(ref array) = expr.kind {
+ for element in array {
+ if_chain! {
+ if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
+ if has_unary_equivalent(op.node) && lhs.span.ctxt() == op.span.ctxt();
+ let space_span = lhs.span.between(op.span);
+ if let Some(space_snippet) = snippet_opt(cx, space_span);
+ let lint_span = lhs.span.with_lo(lhs.span.hi());
+ if space_snippet.contains('\n');
+ if indentation(cx, op.span) <= indentation(cx, lhs.span);
+ then {
+ span_lint_and_note(
+ cx,
+ POSSIBLE_MISSING_COMMA,
+ lint_span,
+ "possibly missing a comma here",
+ None,
+ "to remove this lint, add a comma or write the expr in a single line",
+ );
+ }
+ }
+ }
+ }
+}
+
+fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
+ if_chain! {
+ if !first.span.from_expansion() && !second.span.from_expansion();
+ if matches!(first.kind, ExprKind::If(..));
+ if is_block(second) || is_if(second);
+
+ // Proc-macros can give weird spans. Make sure this is actually an `if`.
+ if is_span_if(cx, first.span);
+
+ // If there is a line break between the two expressions, don't lint.
+ // If there is a non-whitespace character, this span came from a proc-macro.
+ let else_span = first.span.between(second.span);
+ if let Some(else_snippet) = snippet_opt(cx, else_span);
+ if !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace());
+ then {
+ let (looks_like, next_thing) = if is_if(second) {
+ ("an `else if`", "the second `if`")
+ } else {
+ ("an `else {..}`", "the next block")
+ };
+
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_ELSE_FORMATTING,
+ else_span,
- "to remove this lint, add the missing `else` or add a new line before {}",
- next_thing,
++ &format!("this looks like {looks_like} but the `else` is missing"),
+ None,
+ &format!(
++ "to remove this lint, add the missing `else` or add a new line before {next_thing}",
+ ),
+ );
+ }
+ }
+}
+
+fn is_block(expr: &Expr) -> bool {
+ matches!(expr.kind, ExprKind::Block(..))
+}
+
+/// Check if the expression is an `if` or `if let`
+fn is_if(expr: &Expr) -> bool {
+ matches!(expr.kind, ExprKind::If(..))
+}
--- /dev/null
- if let ExprKind::Lit(lit) = &radix.kind;
- if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
+use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::is_integer_literal;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for function invocations of the form `primitive::from_str_radix(s, 10)`
+ ///
+ /// ### Why is this bad?
+ ///
+ /// This specific common use case can be rewritten as `s.parse::<primitive>()`
+ /// (and in most cases, the turbofish can be removed), which reduces code length
+ /// and complexity.
+ ///
+ /// ### Known problems
+ ///
+ /// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly
+ /// in some cases, which is correct but adds unnecessary complexity to the code.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let input: &str = get_input();
+ /// let num = u16::from_str_radix(input, 10)?;
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// let input: &str = get_input();
+ /// let num: u16 = input.parse()?;
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub FROM_STR_RADIX_10,
+ style,
+ "from_str_radix with radix 10"
+}
+
+declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
+
+impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
+
+ // check if the first part of the path is some integer primitive
+ if let TyKind::Path(ty_qpath) = &ty.kind;
+ let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
+ if let def::Res::PrimTy(prim_ty) = ty_res;
+ if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
+
+ // check if the second part of the path indeed calls the associated
+ // function `from_str_radix`
+ if pathseg.ident.name.as_str() == "from_str_radix";
+
+ // check if the second argument is a primitive `10`
- format!("{}.parse::<{}>()", sugg, prim_ty.name_str()),
++ if is_integer_literal(radix, 10);
+
+ then {
+ let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if is_ty_stringish(cx, ty) {
+ expr
+ } else {
+ &src
+ }
+ } else {
+ &src
+ };
+
+ let sugg = Sugg::hir_with_applicability(
+ cx,
+ expr,
+ "<string>",
+ &mut Applicability::MachineApplicable
+ ).maybe_par();
+
+ span_lint_and_sugg(
+ cx,
+ FROM_STR_RADIX_10,
+ exp.span,
+ "this call to `from_str_radix` can be replaced with a call to `str::parse`",
+ "try",
++ format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
+ Applicability::MaybeIncorrect
+ );
+ }
+ }
+ }
+}
+
+/// Checks if a Ty is `String` or `&str`
+fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ is_type_diagnostic_item(cx, ty, sym::String) || is_type_diagnostic_item(cx, ty, sym::str)
+}
--- /dev/null
- use rustc_hir::{self as hir, def::Res, intravisit, QPath};
+use rustc_ast::ast::Attribute;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::{DefIdSet, LocalDefId};
- } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.def_id.def_id).is_none() {
++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};
+
+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::{match_def_path, 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.def_id.to_def_id(), sym::must_use);
+ if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind {
+ let is_public = cx.access_levels.is_exported(item.def_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.def_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.access_levels.is_exported(item.def_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.def_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);
- format!("#[must_use] {}", snippet),
++ } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.def_id.def_id).is_none()
++ {
+ check_must_use_candidate(
+ cx,
+ sig.decl,
+ cx.tcx.hir().body(*body_id),
+ item.span,
+ item.def_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.access_levels.is_exported(item.def_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.def_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.def_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.access_levels.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",
- struct StaticMutVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- mutates_static: bool,
++ 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()) {
+ is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner.def_id).pat_ty(pat), pat.span, tys)
+ } else {
+ false
+ }
+}
+
+static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
+
+fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, 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.at(span), cx.param_env)
+ || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did(), path))
+ && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
+ },
+ ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)),
+ ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
+ ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
+ mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, 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,
+ }
+}
+
- impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
++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,
++ }
+}
+
- if self.mutates_static {
- return;
- }
- match expr.kind {
++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};
+
- if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
++ match e.kind {
+ Call(_, args) => {
+ let mut tys = DefIdSet::default();
+ for arg in args {
- self.cx,
- self.cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
++ if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
+ && is_mutable_ty(
- self.mutates_static = true;
- return;
++ cx,
++ cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
+ arg.span,
+ &mut tys,
+ )
+ && is_mutated_static(arg)
+ {
- if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
++ 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()) {
- self.cx,
- self.cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
++ if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
+ && is_mutable_ty(
- self.mutates_static = true;
- return;
++ cx,
++ cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
+ arg.span,
+ &mut tys,
+ )
+ && is_mutated_static(arg)
+ {
- Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
- self.mutates_static |= is_mutated_static(target);
++ return ControlFlow::Break(());
+ }
+ tys.clear();
+ }
++ ControlFlow::Continue(())
+ },
- _ => {},
++ Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target)
++ if is_mutated_static(target) =>
++ {
++ ControlFlow::Break(())
+ },
- }
- }
-
- 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 {
- let mut v = StaticMutVisitor {
- cx,
- mutates_static: false,
- };
- intravisit::walk_expr(&mut v, body.value);
- v.mutates_static
++ _ => ControlFlow::Continue(()),
+ }
++ })
++ .is_some()
+}
--- /dev/null
- let expr = &body.value;
+use rustc_hir::{self as hir, intravisit, HirIdSet};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::def_id::LocalDefId;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::type_is_unsafe_function;
++use clippy_utils::visitors::for_each_expr_with_closures;
+use clippy_utils::{iter_input_pats, path_to_local};
+
++use core::ops::ControlFlow;
++
+use super::NOT_UNSAFE_PTR_ARG_DEREF;
+
+pub(super) fn check_fn<'tcx>(
+ cx: &LateContext<'tcx>,
+ kind: intravisit::FnKind<'tcx>,
+ decl: &'tcx hir::FnDecl<'tcx>,
+ body: &'tcx hir::Body<'tcx>,
+ hir_id: hir::HirId,
+) {
+ let unsafety = match kind {
+ intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }) => unsafety,
+ intravisit::FnKind::Method(_, sig) => sig.header.unsafety,
+ intravisit::FnKind::Closure => return,
+ };
+
+ check_raw_ptr(cx, unsafety, decl, body, cx.tcx.hir().local_def_id(hir_id));
+}
+
+pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ if let hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(eid)) = item.kind {
+ let body = cx.tcx.hir().body(eid);
+ check_raw_ptr(cx, sig.header.unsafety, sig.decl, body, item.def_id.def_id);
+ }
+}
+
+fn check_raw_ptr<'tcx>(
+ cx: &LateContext<'tcx>,
+ unsafety: hir::Unsafety,
+ decl: &'tcx hir::FnDecl<'tcx>,
+ body: &'tcx hir::Body<'tcx>,
+ def_id: LocalDefId,
+) {
- let typeck_results = cx.tcx.typeck_body(body.id());
- let mut v = DerefVisitor {
- cx,
- ptrs: raw_ptrs,
- typeck_results,
- };
-
- intravisit::walk_expr(&mut v, expr);
+ if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(def_id) {
+ let raw_ptrs = iter_input_pats(decl, body)
+ .filter_map(|arg| raw_ptr_arg(cx, arg))
+ .collect::<HirIdSet>();
+
+ if !raw_ptrs.is_empty() {
- struct DerefVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- ptrs: HirIdSet,
- typeck_results: &'a ty::TypeckResults<'tcx>,
- }
-
- impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- match expr.kind {
- hir::ExprKind::Call(f, args) => {
- let ty = self.typeck_results.expr_ty(f);
-
- if type_is_unsafe_function(self.cx, ty) {
- for arg in args {
- self.check_arg(arg);
- }
- }
- },
- hir::ExprKind::MethodCall(_, receiver, args, _) => {
- let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
- let base_type = self.cx.tcx.type_of(def_id);
-
- if type_is_unsafe_function(self.cx, base_type) {
- self.check_arg(receiver);
- for arg in args {
- self.check_arg(arg);
- }
- }
- },
- hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => self.check_arg(ptr),
- _ => (),
- }
-
- intravisit::walk_expr(self, expr);
- }
- }
-
- impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
- fn check_arg(&self, ptr: &hir::Expr<'_>) {
- if let Some(id) = path_to_local(ptr) {
- if self.ptrs.contains(&id) {
- span_lint(
- self.cx,
- NOT_UNSAFE_PTR_ARG_DEREF,
- ptr.span,
- "this public function might dereference a raw pointer but is not marked `unsafe`",
- );
- }
- }
++ let typeck = cx.tcx.typeck_body(body.id());
++ let _: Option<!> = for_each_expr_with_closures(cx, body.value, |e| {
++ match e.kind {
++ hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => {
++ for arg in args {
++ check_arg(cx, &raw_ptrs, arg);
++ }
++ },
++ hir::ExprKind::MethodCall(_, recv, args, _) => {
++ let def_id = typeck.type_dependent_def_id(e.hir_id).unwrap();
++ if cx.tcx.fn_sig(def_id).skip_binder().unsafety == hir::Unsafety::Unsafe {
++ check_arg(cx, &raw_ptrs, recv);
++ for arg in args {
++ check_arg(cx, &raw_ptrs, arg);
++ }
++ }
++ },
++ hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => check_arg(cx, &raw_ptrs, ptr),
++ _ => (),
++ }
++ ControlFlow::Continue(())
++ });
+ }
+ }
+}
+
+fn raw_ptr_arg(cx: &LateContext<'_>, arg: &hir::Param<'_>) -> Option<hir::HirId> {
+ if let (&hir::PatKind::Binding(_, id, _, _), Some(&ty::RawPtr(_))) = (
+ &arg.pat.kind,
+ cx.maybe_typeck_results()
+ .map(|typeck_results| typeck_results.pat_ty(arg.pat).kind()),
+ ) {
+ Some(id)
+ } else {
+ None
+ }
+}
+
++fn check_arg(cx: &LateContext<'_>, raw_ptrs: &HirIdSet, arg: &hir::Expr<'_>) {
++ if path_to_local(arg).map_or(false, |id| raw_ptrs.contains(&id)) {
++ span_lint(
++ cx,
++ NOT_UNSAFE_PTR_ARG_DEREF,
++ arg.span,
++ "this public function might dereference a raw pointer but is not marked `unsafe`",
++ );
+ }
+}
--- /dev/null
- &format!(
- "this function has too many arguments ({}/{})",
- args, too_many_arguments_threshold
- ),
+use rustc_hir::{self as hir, intravisit};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+use rustc_target::spec::abi::Abi;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_trait_impl_item;
+
+use super::TOO_MANY_ARGUMENTS;
+
+pub(super) fn check_fn(
+ cx: &LateContext<'_>,
+ kind: intravisit::FnKind<'_>,
+ decl: &hir::FnDecl<'_>,
+ span: Span,
+ hir_id: hir::HirId,
+ too_many_arguments_threshold: u64,
+) {
+ // don't warn for implementations, it's not their fault
+ if !is_trait_impl_item(cx, hir_id) {
+ // don't lint extern functions decls, it's not their fault either
+ match kind {
+ intravisit::FnKind::Method(
+ _,
+ &hir::FnSig {
+ header: hir::FnHeader { abi: Abi::Rust, .. },
+ ..
+ },
+ )
+ | intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }) => check_arg_number(
+ cx,
+ decl,
+ span.with_hi(decl.output.span().hi()),
+ too_many_arguments_threshold,
+ ),
+ _ => {},
+ }
+ }
+}
+
+pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>, too_many_arguments_threshold: u64) {
+ if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
+ // don't lint extern functions decls, it's not their fault
+ if sig.header.abi == Abi::Rust {
+ check_arg_number(
+ cx,
+ sig.decl,
+ item.span.with_hi(sig.decl.output.span().hi()),
+ too_many_arguments_threshold,
+ );
+ }
+ }
+}
+
+fn check_arg_number(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span, too_many_arguments_threshold: u64) {
+ let args = decl.inputs.len() as u64;
+ if args > too_many_arguments_threshold {
+ span_lint(
+ cx,
+ TOO_MANY_ARGUMENTS,
+ fn_span,
++ &format!("this function has too many arguments ({args}/{too_many_arguments_threshold})"),
+ );
+ }
+}
--- /dev/null
- &format!(
- "this function has too many lines ({}/{})",
- line_count, too_many_lines_threshold
- ),
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_span::Span;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet_opt;
+
+use super::TOO_MANY_LINES;
+
+pub(super) fn check_fn(
+ cx: &LateContext<'_>,
+ kind: FnKind<'_>,
+ span: Span,
+ body: &hir::Body<'_>,
+ too_many_lines_threshold: u64,
+) {
+ // Closures must be contained in a parent body, which will be checked for `too_many_lines`.
+ // Don't check closures for `too_many_lines` to avoid duplicated lints.
+ if matches!(kind, FnKind::Closure) || in_external_macro(cx.sess(), span) {
+ return;
+ }
+
+ let code_snippet = match snippet_opt(cx, body.value.span) {
+ Some(s) => s,
+ _ => return,
+ };
+ let mut line_count: u64 = 0;
+ let mut in_comment = false;
+ let mut code_in_line;
+
+ let function_lines = if matches!(body.value.kind, hir::ExprKind::Block(..))
+ && code_snippet.as_bytes().first().copied() == Some(b'{')
+ && code_snippet.as_bytes().last().copied() == Some(b'}')
+ {
+ // Removing the braces from the enclosing block
+ &code_snippet[1..code_snippet.len() - 1]
+ } else {
+ &code_snippet
+ }
+ .trim() // Remove leading and trailing blank lines
+ .lines();
+
+ for mut line in function_lines {
+ code_in_line = false;
+ loop {
+ line = line.trim_start();
+ if line.is_empty() {
+ break;
+ }
+ if in_comment {
+ if let Some(i) = line.find("*/") {
+ line = &line[i + 2..];
+ in_comment = false;
+ continue;
+ }
+ } else {
+ let multi_idx = line.find("/*").unwrap_or(line.len());
+ let single_idx = line.find("//").unwrap_or(line.len());
+ code_in_line |= multi_idx > 0 && single_idx > 0;
+ // Implies multi_idx is below line.len()
+ if multi_idx < single_idx {
+ line = &line[multi_idx + 2..];
+ in_comment = true;
+ continue;
+ }
+ }
+ break;
+ }
+ if code_in_line {
+ line_count += 1;
+ }
+ }
+
+ if line_count > too_many_lines_threshold {
+ span_lint(
+ cx,
+ TOO_MANY_LINES,
+ span,
++ &format!("this function has too many lines ({line_count}/{too_many_lines_threshold})"),
+ );
+ }
+}
--- /dev/null
- use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs, peel_blocks};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::eager_or_lazy::switch_to_eager_eval;
+use clippy_utils::source::snippet_with_macro_callsite;
- && let ExprKind::Path(ref then_call_qpath) = then_call.kind
- && is_lang_ctor(cx, then_call_qpath, OptionSome)
- && let ExprKind::Path(ref qpath) = peel_blocks(els).kind
- && is_lang_ctor(cx, qpath, OptionNone)
++use clippy_utils::{
++ contains_return, higher, is_else_clause, is_res_lang_ctor, meets_msrv, msrvs, path_res, peel_blocks,
++};
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{Expr, ExprKind, 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};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`.
+ ///
+ /// ### Why is this bad?
+ /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity.
+ /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated
+ /// in comparison to `bool::then`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = vec![0];
+ /// let a = if v.is_empty() {
+ /// println!("true!");
+ /// Some(42)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # let v = vec![0];
+ /// let a = v.is_empty().then(|| {
+ /// println!("true!");
+ /// 42
+ /// });
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub IF_THEN_SOME_ELSE_NONE,
+ restriction,
+ "Finds if-else that could be written using either `bool::then` or `bool::then_some`"
+}
+
+pub struct IfThenSomeElseNone {
+ msrv: Option<RustcVersion>,
+}
+
+impl IfThenSomeElseNone {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]);
+
+impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if !meets_msrv(self.msrv, msrvs::BOOL_THEN) {
+ return;
+ }
+
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ // We only care about the top-most `if` in the chain
+ if is_else_clause(cx.tcx, expr) {
+ return;
+ }
+
+ if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr)
+ && let ExprKind::Block(then_block, _) = then.kind
+ && let Some(then_expr) = then_block.expr
+ && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind
- format!("({})", cond_snip)
++ && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome)
++ && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone)
+ && !stmts_contains_early_return(then_block.stmts)
+ {
+ let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]");
+ let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) {
- format!("{{ /* snippet */ {} }}", arg_snip)
++ format!("({cond_snip})")
+ } else {
+ cond_snip.into_owned()
+ };
+ let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, "");
+ let mut method_body = if then_block.stmts.is_empty() {
+ arg_snip.into_owned()
+ } else {
- "consider using `bool::{}` like: `{}.{}({})`",
- method_name, cond_snip, method_name, method_body,
++ format!("{{ /* snippet */ {arg_snip} }}")
+ };
+ let method_name = if switch_to_eager_eval(cx, expr) && meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
+ "then_some"
+ } else {
+ method_body.insert_str(0, "|| ");
+ "then"
+ };
+
+ let help = format!(
- &format!("this could be simplified with `bool::{}`", method_name),
++ "consider using `bool::{method_name}` like: `{cond_snip}.{method_name}({method_body})`",
+ );
+ span_lint_and_help(
+ cx,
+ IF_THEN_SOME_ELSE_NONE,
+ expr.span,
++ &format!("this could be simplified with `bool::{method_name}`"),
+ None,
+ &help,
+ );
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool {
+ stmts.iter().any(|stmt| {
+ let Stmt { kind: StmtKind::Semi(e), .. } = stmt else { return false };
+
+ contains_return(e)
+ })
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+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;
- "<{}{}S: ::std::hash::BuildHasher{}>",
- generics_snip,
+
+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<'tcx>(
+ cx: &LateContext<'tcx>,
+ 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!(
- ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
- ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
++ "<{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.access_levels.is_exported(item.def_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
- visitors::expr_visitor_no_bodies,
+use clippy_utils::{
+ diagnostics::span_lint_hir_and_then,
+ get_async_fn_body, is_async_fn,
+ source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
- use rustc_hir::intravisit::{FnKind, Visitor};
++ visitors::for_each_expr,
+};
++use core::ops::ControlFlow;
+use rustc_errors::Applicability;
- diag.span_suggestion(span, "add `return` as shown", format!("return {}", snip), app);
++use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
+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::{Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for missing return statements at the end of a block.
+ ///
+ /// ### Why is this bad?
+ /// Actually omitting the return keyword is idiomatic Rust code. Programmers
+ /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss
+ /// the last returning statement because the only difference is a missing `;`. Especially in bigger
+ /// code with multiple return paths having a `return` keyword makes it easier to find the
+ /// corresponding statements.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ /// add return
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
+ #[clippy::version = "1.33.0"]
+ pub IMPLICIT_RETURN,
+ restriction,
+ "use a return statement like `return expr` instead of an expression"
+}
+
+declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
+
+fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_applicability(cx, span, "..", &mut app);
+ span_lint_hir_and_then(
+ cx,
+ IMPLICIT_RETURN,
+ emission_place,
+ span,
+ "missing `return` statement",
+ |diag| {
- format!("return {}", snip),
++ diag.span_suggestion(span, "add `return` as shown", format!("return {snip}"), app);
+ },
+ );
+}
+
+fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
+ span_lint_hir_and_then(
+ cx,
+ IMPLICIT_RETURN,
+ emission_place,
+ break_span,
+ "missing `return` statement",
+ |diag| {
+ diag.span_suggestion(
+ break_span,
+ "change `break` to `return` as shown",
- expr_visitor_no_bodies(|e| {
++ format!("return {snip}"),
+ app,
+ );
+ },
+ );
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum LintLocation {
+ /// The lint was applied to a parent expression.
+ Parent,
+ /// The lint was applied to this expression, a child, or not applied.
+ Inner,
+}
+impl LintLocation {
+ fn still_parent(self, b: bool) -> Self {
+ if b { self } else { Self::Inner }
+ }
+
+ fn is_parent(self) -> bool {
+ self == Self::Parent
+ }
+}
+
+// Gets the call site if the span is in a child context. Otherwise returns `None`.
+fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option<Span> {
+ (span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span))
+}
+
+fn lint_implicit_returns(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ // The context of the function body.
+ ctxt: SyntaxContext,
+ // Whether the expression is from a macro expansion.
+ call_site_span: Option<Span>,
+) -> LintLocation {
+ match expr.kind {
+ ExprKind::Block(
+ Block {
+ expr: Some(block_expr), ..
+ },
+ _,
+ ) => lint_implicit_returns(
+ cx,
+ block_expr,
+ ctxt,
+ call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)),
+ )
+ .still_parent(call_site_span.is_some()),
+
+ ExprKind::If(_, then_expr, Some(else_expr)) => {
+ // Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't
+ // bother checking.
+ let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some());
+ if res.is_parent() {
+ // The return was added as a parent of this if expression.
+ return res;
+ }
+ lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some())
+ },
+
+ ExprKind::Match(_, arms, _) => {
+ for arm in arms {
+ let res = lint_implicit_returns(
+ cx,
+ arm.body,
+ ctxt,
+ call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)),
+ )
+ .still_parent(call_site_span.is_some());
+ if res.is_parent() {
+ // The return was added as a parent of this match expression.
+ return res;
+ }
+ }
+ LintLocation::Inner
+ },
+
+ ExprKind::Loop(block, ..) => {
+ let mut add_return = false;
- true
- })
- .visit_block(block);
++ let _: Option<!> = for_each_expr(block, |e| {
+ if let ExprKind::Break(dest, sub_expr) = e.kind {
+ if dest.target_id.ok() == Some(expr.hir_id) {
+ if call_site_span.is_none() && e.span.ctxt() == ctxt {
+ // At this point sub_expr can be `None` in async functions which either diverge, or return
+ // the unit type.
+ if let Some(sub_expr) = sub_expr {
+ lint_break(cx, e.hir_id, e.span, sub_expr.span);
+ }
+ } else {
+ // the break expression is from a macro call, add a return to the loop
+ add_return = true;
+ }
+ }
+ }
++ ControlFlow::Continue(())
++ });
+ if add_return {
+ #[expect(clippy::option_if_let_else)]
+ if let Some(span) = call_site_span {
+ lint_return(cx, expr.hir_id, span);
+ LintLocation::Parent
+ } else {
+ lint_return(cx, expr.hir_id, expr.span);
+ LintLocation::Inner
+ }
+ } else {
+ LintLocation::Inner
+ }
+ },
+
+ // If expressions without an else clause, and blocks without a final expression can only be the final expression
+ // if they are divergent, or return the unit type.
+ ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => {
+ LintLocation::Inner
+ },
+
+ // Any divergent expression doesn't need a return statement.
+ ExprKind::MethodCall(..)
+ | ExprKind::Call(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Index(..)
+ if cx.typeck_results().expr_ty(expr).is_never() =>
+ {
+ LintLocation::Inner
+ },
+
+ _ =>
+ {
+ #[expect(clippy::option_if_let_else)]
+ if let Some(span) = call_site_span {
+ lint_return(cx, expr.hir_id, span);
+ LintLocation::Parent
+ } else {
+ lint_return(cx, expr.hir_id, expr.span);
+ LintLocation::Inner
+ }
+ },
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_)))
+ || span.ctxt() != body.value.span.ctxt()
+ || in_external_macro(cx.sess(), span)
+ {
+ return;
+ }
+
+ let res_ty = cx.typeck_results().expr_ty(body.value);
+ if res_ty.is_unit() || res_ty.is_never() {
+ return;
+ }
+
+ let expr = if is_async_fn(kind) {
+ match get_async_fn_body(cx.tcx, body) {
+ Some(e) => e,
+ None => return,
+ }
+ } else {
+ body.value
+ };
+ lint_implicit_returns(cx, expr, expr.span.ctxt(), None);
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::consts::{constant, Constant};
++use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::get_parent_expr;
++use clippy_utils::source::snippet_with_applicability;
++use if_chain::if_chain;
++use rustc_ast::ast::{LitIntType, LitKind};
++use rustc_errors::Applicability;
++use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty::{Int, IntTy, Ty, Uint, UintTy};
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for implicit saturating addition.
++ ///
++ /// ### Why is this bad?
++ /// The built-in function is more readable and may be faster.
++ ///
++ /// ### Example
++ /// ```rust
++ ///let mut u:u32 = 7000;
++ ///
++ /// if u != u32::MAX {
++ /// u += 1;
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ ///let mut u:u32 = 7000;
++ ///
++ /// u = u.saturating_add(1);
++ /// ```
++ #[clippy::version = "1.65.0"]
++ pub IMPLICIT_SATURATING_ADD,
++ style,
++ "Perform saturating addition instead of implicitly checking max bound of data type"
++}
++declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
++
++impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
++ if_chain! {
++ if let ExprKind::If(cond, then, None) = expr.kind;
++ if let ExprKind::DropTemps(expr1) = cond.kind;
++ if let Some((c, op_node, l)) = get_const(cx, expr1);
++ if let BinOpKind::Ne | BinOpKind::Lt = op_node;
++ if let ExprKind::Block(block, None) = then.kind;
++ if let Block {
++ stmts:
++ [Stmt
++ { kind: StmtKind::Expr(ex) | StmtKind::Semi(ex), .. }],
++ expr: None, ..} |
++ Block { stmts: [], expr: Some(ex), ..} = block;
++ if let ExprKind::AssignOp(op1, target, value) = ex.kind;
++ let ty = cx.typeck_results().expr_ty(target);
++ if Some(c) == get_int_max(ty);
++ if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target);
++ if BinOpKind::Add == op1.node;
++ if let ExprKind::Lit(ref lit) = value.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node;
++ if block.expr.is_none();
++ then {
++ let mut app = Applicability::MachineApplicable;
++ let code = snippet_with_applicability(cx, target.span, "_", &mut app);
++ let sugg = if let Some(parent) = get_parent_expr(cx, expr) && let ExprKind::If(_cond, _then, Some(else_)) = parent.kind && else_.hir_id == expr.hir_id {format!("{{{code} = {code}.saturating_add(1); }}")} else {format!("{code} = {code}.saturating_add(1);")};
++ span_lint_and_sugg(cx, IMPLICIT_SATURATING_ADD, expr.span, "manual saturating add detected", "use instead", sugg, app);
++ }
++ }
++ }
++}
++
++fn get_int_max(ty: Ty<'_>) -> Option<u128> {
++ match ty.peel_refs().kind() {
++ Int(IntTy::I8) => i8::max_value().try_into().ok(),
++ Int(IntTy::I16) => i16::max_value().try_into().ok(),
++ Int(IntTy::I32) => i32::max_value().try_into().ok(),
++ Int(IntTy::I64) => i64::max_value().try_into().ok(),
++ Int(IntTy::I128) => i128::max_value().try_into().ok(),
++ Int(IntTy::Isize) => isize::max_value().try_into().ok(),
++ Uint(UintTy::U8) => u8::max_value().try_into().ok(),
++ Uint(UintTy::U16) => u16::max_value().try_into().ok(),
++ Uint(UintTy::U32) => u32::max_value().try_into().ok(),
++ Uint(UintTy::U64) => u64::max_value().try_into().ok(),
++ Uint(UintTy::U128) => Some(u128::max_value()),
++ Uint(UintTy::Usize) => usize::max_value().try_into().ok(),
++ _ => None,
++ }
++}
++
++fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> {
++ if let ExprKind::Binary(op, l, r) = expr.kind {
++ let tr = cx.typeck_results();
++ if let Some((Constant::Int(c), _)) = constant(cx, tr, r) {
++ return Some((c, op.node, l));
++ };
++ if let Some((Constant::Int(c), _)) = constant(cx, tr, l) {
++ return Some((c, invert_op(op.node)?, r));
++ }
++ }
++ None
++}
++
++fn invert_op(op: BinOpKind) -> Option<BinOpKind> {
++ use rustc_hir::BinOpKind::{Ge, Gt, Le, Lt, Ne};
++ match op {
++ Lt => Some(Gt),
++ Le => Some(Ge),
++ Ne => Some(Ne),
++ Ge => Some(Le),
++ Gt => Some(Lt),
++ _ => None,
++ }
++}
--- /dev/null
- use clippy_utils::{higher, peel_blocks_with_stmt, SpanlessEq};
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if_chain! {
- if BinOpKind::Sub == op1.node;
- // Check if literal being subtracted is one
- if let ExprKind::Lit(ref lit1) = value.kind;
- if let LitKind::Int(1, _) = lit1.node;
- then {
- Some(target)
- } else {
- None
- }
- }
++use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for implicit saturating subtraction.
+ ///
+ /// ### Why is this bad?
+ /// Simplicity and readability. Instead we can easily use an builtin function.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let end: u32 = 10;
+ /// # let start: u32 = 5;
+ /// let mut i: u32 = end - start;
+ ///
+ /// if i != 0 {
+ /// i -= 1;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let end: u32 = 10;
+ /// # let start: u32 = 5;
+ /// let mut i: u32 = end - start;
+ ///
+ /// i = i.saturating_sub(1);
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub IMPLICIT_SATURATING_SUB,
+ pedantic,
+ "Perform saturating subtraction instead of implicitly checking lower bound of data type"
+}
+
+declare_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB]);
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
+
+ // Check if the conditional expression is a binary operation
+ if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
+
+ // Ensure that the binary operator is >, !=, or <
+ if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
+
+ // Check if assign operation is done
+ if let Some(target) = subtracts_one(cx, then);
+
+ // Extracting out the variable name
+ if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
+
+ then {
+ // Handle symmetric conditions in the if statement
+ let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
+ if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
+ (cond_left, cond_right)
+ } else {
+ return;
+ }
+ } else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
+ if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
+ (cond_right, cond_left)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ };
+
+ // Check if the variable in the condition statement is an integer
+ if !cx.typeck_results().expr_ty(cond_var).is_integral() {
+ return;
+ }
+
+ // Get the variable name
+ let var_name = ares_path.segments[0].ident.name.as_str();
+ match cond_num_val.kind {
+ ExprKind::Lit(ref cond_lit) => {
+ // Check if the constant is zero
+ if let LitKind::Int(0, _) = cond_lit.node {
+ if cx.typeck_results().expr_ty(cond_left).is_signed() {
+ } else {
+ print_lint_and_sugg(cx, var_name, expr);
+ };
+ }
+ },
+ ExprKind::Path(QPath::TypeRelative(_, name)) => {
+ if_chain! {
+ if name.ident.as_str() == "MIN";
+ if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(const_id);
+ if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
+ if cx.tcx.type_of(impl_id).is_integral();
+ then {
+ print_lint_and_sugg(cx, var_name, expr)
+ }
+ }
+ },
+ ExprKind::Call(func, []) => {
+ if_chain! {
+ if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind;
+ if name.ident.as_str() == "min_value";
+ if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(func_id);
+ if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
+ if cx.tcx.type_of(impl_id).is_integral();
+ then {
+ print_lint_and_sugg(cx, var_name, expr)
+ }
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
+fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
+ match peel_blocks_with_stmt(expr).kind {
+ ExprKind::AssignOp(ref op1, target, value) => {
- if let ExprKind::Lit(ref lit1) = right1.kind;
- if let LitKind::Int(1, _) = lit1.node;
++ // Check if literal being subtracted is one
++ (BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target)
+ },
+ ExprKind::Assign(target, value, _) => {
+ if_chain! {
+ if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
+ if BinOpKind::Sub == op1.node;
+
+ if SpanlessEq::new(cx).eq_expr(left1, target);
+
- format!("{} = {}.saturating_sub({});", var_name, var_name, '1'),
++ if is_integer_literal(right1, 1);
+ then {
+ Some(target)
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ }
+}
+
+fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: &str, expr: &Expr<'_>) {
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_SATURATING_SUB,
+ expr.span,
+ "implicitly performing saturating subtraction",
+ "try",
++ format!("{var_name} = {var_name}.saturating_sub({});", '1'),
+ Applicability::MachineApplicable,
+ );
+}
--- /dev/null
- let _ = write!(fields_snippet, "{}, ", ident);
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Symbol;
+use std::fmt::Write as _;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for struct constructors where all fields are shorthand and
+ /// the order of the field init shorthand in the constructor is inconsistent
+ /// with the order in the struct definition.
+ ///
+ /// ### Why is this bad?
+ /// Since the order of fields in a constructor doesn't affect the
+ /// resulted instance as the below example indicates,
+ ///
+ /// ```rust
+ /// #[derive(Debug, PartialEq, Eq)]
+ /// struct Foo {
+ /// x: i32,
+ /// y: i32,
+ /// }
+ /// let x = 1;
+ /// let y = 2;
+ ///
+ /// // This assertion never fails:
+ /// assert_eq!(Foo { x, y }, Foo { y, x });
+ /// ```
+ ///
+ /// inconsistent order can be confusing and decreases readability and consistency.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo {
+ /// x: i32,
+ /// y: i32,
+ /// }
+ /// let x = 1;
+ /// let y = 2;
+ ///
+ /// Foo { y, x };
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # struct Foo {
+ /// # x: i32,
+ /// # y: i32,
+ /// # }
+ /// # let x = 1;
+ /// # let y = 2;
+ /// Foo { x, y };
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub INCONSISTENT_STRUCT_CONSTRUCTOR,
+ pedantic,
+ "the order of the field init shorthand is inconsistent with the order in the struct definition"
+}
+
+declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
+
+impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ if let ExprKind::Struct(qpath, fields, base) = expr.kind;
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let Some(adt_def) = ty.ty_adt_def();
+ if adt_def.is_struct();
+ if let Some(variant) = adt_def.variants().iter().next();
+ if fields.iter().all(|f| f.is_shorthand);
+ then {
+ let mut def_order_map = FxHashMap::default();
+ for (idx, field) in variant.fields.iter().enumerate() {
+ def_order_map.insert(field.name, idx);
+ }
+
+ if is_consistent_order(fields, &def_order_map) {
+ return;
+ }
+
+ let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
+ ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
+
+ let mut fields_snippet = String::new();
+ let (last_ident, idents) = ordered_fields.split_last().unwrap();
+ for ident in idents {
- let sugg = format!("{} {{ {}{} }}",
++ let _ = write!(fields_snippet, "{ident}, ");
+ }
+ fields_snippet.push_str(&last_ident.to_string());
+
+ let base_snippet = if let Some(base) = base {
+ format!(", ..{}", snippet(cx, base.span, ".."))
+ } else {
+ String::new()
+ };
+
- fields_snippet,
- base_snippet,
++ let sugg = format!("{} {{ {fields_snippet}{base_snippet} }}",
+ snippet(cx, qpath.span(), ".."),
+ );
+
+ span_lint_and_sugg(
+ cx,
+ INCONSISTENT_STRUCT_CONSTRUCTOR,
+ expr.span,
+ "struct constructor field order is inconsistent with struct definition field order",
+ "try",
+ sugg,
+ Applicability::MachineApplicable,
+ )
+ }
+ }
+ }
+}
+
+// Check whether the order of the fields in the constructor is consistent with the order in the
+// definition.
+fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool {
+ let mut cur_idx = usize::MIN;
+ for f in fields {
+ let next_idx = def_order_map[&f.ident.name];
+ if cur_idx > next_idx {
+ return false;
+ }
+ cur_idx = next_idx;
+ }
+
+ true
+}
--- /dev/null
- let value_name = |index| format!("{}_{}", slice.ident.name, index);
+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<_>>();
+
- format!("{}{}", opt_ref, value_name(index))
++ 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<'a, 'tcx>(
+ cx: &'a 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
- use clippy_utils::{higher, match_def_path, path_def_id, paths};
+use clippy_utils::diagnostics::span_lint;
++use clippy_utils::higher;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
- ExprKind::Call(path, _) => path_def_id(cx, path)
- .map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT))
- .into(),
+use rustc_hir::{BorrowKind, Closure, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::{sym, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for iteration that is guaranteed to be infinite.
+ ///
+ /// ### Why is this bad?
+ /// While there may be places where this is acceptable
+ /// (e.g., in event streams), in most cases this is simply an error.
+ ///
+ /// ### Example
+ /// ```no_run
+ /// use std::iter;
+ ///
+ /// iter::repeat(1_u8).collect::<Vec<_>>();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INFINITE_ITER,
+ correctness,
+ "infinite iteration"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for iteration that may be infinite.
+ ///
+ /// ### Why is this bad?
+ /// While there may be places where this is acceptable
+ /// (e.g., in event streams), in most cases this is simply an error.
+ ///
+ /// ### Known problems
+ /// The code may have a condition to stop iteration, but
+ /// this lint is not clever enough to analyze it.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let infinite_iter = 0..;
+ /// # #[allow(unused)]
+ /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5));
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MAYBE_INFINITE_ITER,
+ pedantic,
+ "possible infinite iteration"
+}
+
+declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]);
+
+impl<'tcx> LateLintPass<'tcx> for InfiniteIter {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (lint, msg) = match complete_infinite_iter(cx, expr) {
+ Infinite => (INFINITE_ITER, "infinite iteration detected"),
+ MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
+ Finite => {
+ return;
+ },
+ };
+ span_lint(cx, lint, expr.span, msg);
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Finiteness {
+ Infinite,
+ MaybeInfinite,
+ Finite,
+}
+
+use self::Finiteness::{Finite, Infinite, MaybeInfinite};
+
+impl Finiteness {
+ #[must_use]
+ fn and(self, b: Self) -> Self {
+ match (self, b) {
+ (Finite, _) | (_, Finite) => Finite,
+ (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
+ _ => Infinite,
+ }
+ }
+
+ #[must_use]
+ fn or(self, b: Self) -> Self {
+ match (self, b) {
+ (Infinite, _) | (_, Infinite) => Infinite,
+ (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
+ _ => Finite,
+ }
+ }
+}
+
+impl From<bool> for Finiteness {
+ #[must_use]
+ fn from(b: bool) -> Self {
+ if b { Infinite } else { Finite }
+ }
+}
+
+/// This tells us what to look for to know if the iterator returned by
+/// this method is infinite
+#[derive(Copy, Clone)]
+enum Heuristic {
+ /// infinite no matter what
+ Always,
+ /// infinite if the first argument is
+ First,
+ /// infinite if any of the supplied arguments is
+ Any,
+ /// infinite if all of the supplied arguments are
+ All,
+}
+
+use self::Heuristic::{All, Always, Any, First};
+
+/// a slice of (method name, number of args, heuristic, bounds) tuples
+/// that will be used to determine whether the method in question
+/// returns an infinite or possibly infinite iterator. The finiteness
+/// is an upper bound, e.g., some methods can return a possibly
+/// infinite iterator at worst, e.g., `take_while`.
+const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
+ ("zip", 1, All, Infinite),
+ ("chain", 1, Any, Infinite),
+ ("cycle", 0, Always, Infinite),
+ ("map", 1, First, Infinite),
+ ("by_ref", 0, First, Infinite),
+ ("cloned", 0, First, Infinite),
+ ("rev", 0, First, Infinite),
+ ("inspect", 0, First, Infinite),
+ ("enumerate", 0, First, Infinite),
+ ("peekable", 1, First, Infinite),
+ ("fuse", 0, First, Infinite),
+ ("skip", 1, First, Infinite),
+ ("skip_while", 0, First, Infinite),
+ ("filter", 1, First, Infinite),
+ ("filter_map", 1, First, Infinite),
+ ("flat_map", 1, First, Infinite),
+ ("unzip", 0, First, Infinite),
+ ("take_while", 1, First, MaybeInfinite),
+ ("scan", 2, First, MaybeInfinite),
+];
+
+fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
+ match expr.kind {
+ ExprKind::MethodCall(method, receiver, args, _) => {
+ for &(name, len, heuristic, cap) in &HEURISTICS {
+ if method.ident.name.as_str() == name && args.len() == len {
+ return (match heuristic {
+ Always => Infinite,
+ First => is_infinite(cx, receiver),
+ Any => is_infinite(cx, receiver).or(is_infinite(cx, &args[0])),
+ All => is_infinite(cx, receiver).and(is_infinite(cx, &args[0])),
+ })
+ .and(cap);
+ }
+ }
+ if method.ident.name == sym!(flat_map) && args.len() == 1 {
+ if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
+ let body = cx.tcx.hir().body(body);
+ return is_infinite(cx, body.value);
+ }
+ }
+ Finite
+ },
+ ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
+ ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
++ ExprKind::Call(path, _) => {
++ if let ExprKind::Path(ref qpath) = path.kind {
++ cx.qpath_res(qpath, path.hir_id)
++ .opt_def_id()
++ .map_or(false, |id| cx.tcx.is_diagnostic_item(sym::iter_repeat, id))
++ .into()
++ } else {
++ Finite
++ }
++ },
+ ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
+ _ => Finite,
+ }
+}
+
+/// the names and argument lengths of methods that *may* exhaust their
+/// iterators
+const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [
+ ("find", 1),
+ ("rfind", 1),
+ ("position", 1),
+ ("rposition", 1),
+ ("any", 1),
+ ("all", 1),
+];
+
+/// the names and argument lengths of methods that *always* exhaust
+/// their iterators
+const COMPLETING_METHODS: [(&str, usize); 12] = [
+ ("count", 0),
+ ("fold", 2),
+ ("for_each", 1),
+ ("partition", 1),
+ ("max", 0),
+ ("max_by", 1),
+ ("max_by_key", 1),
+ ("min", 0),
+ ("min_by", 1),
+ ("min_by_key", 1),
+ ("sum", 0),
+ ("product", 0),
+];
+
+/// the paths of types that are known to be infinitely allocating
+const INFINITE_COLLECTORS: &[Symbol] = &[
+ sym::BinaryHeap,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::HashMap,
+ sym::HashSet,
+ sym::LinkedList,
+ sym::Vec,
+ sym::VecDeque,
+];
+
+fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
+ match expr.kind {
+ ExprKind::MethodCall(method, receiver, args, _) => {
+ for &(name, len) in &COMPLETING_METHODS {
+ if method.ident.name.as_str() == name && args.len() == len {
+ return is_infinite(cx, receiver);
+ }
+ }
+ for &(name, len) in &POSSIBLY_COMPLETING_METHODS {
+ if method.ident.name.as_str() == name && args.len() == len {
+ return MaybeInfinite.and(is_infinite(cx, receiver));
+ }
+ }
+ if method.ident.name == sym!(last) && args.is_empty() {
+ let not_double_ended = cx
+ .tcx
+ .get_diagnostic_item(sym::DoubleEndedIterator)
+ .map_or(false, |id| {
+ !implements_trait(cx, cx.typeck_results().expr_ty(receiver), id, &[])
+ });
+ if not_double_ended {
+ return is_infinite(cx, receiver);
+ }
+ } else if method.ident.name == sym!(collect) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if INFINITE_COLLECTORS
+ .iter()
+ .any(|diag_item| is_type_diagnostic_item(cx, ty, *diag_item))
+ {
+ return is_infinite(cx, receiver);
+ }
+ }
+ },
+ ExprKind::Binary(op, l, r) => {
+ if op.node.is_comparison() {
+ return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
+ }
+ }, // TODO: ExprKind::Loop + Match
+ _ => (),
+ }
+ Finite
+}
--- /dev/null
- use clippy_utils::{get_trait_def_id, paths, return_ty, trait_ref_of_method};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
- let display_trait_id = get_trait_def_id(cx, &paths::DISPLAY_TRAIT).expect("Failed to get trait ID of `Display`!");
++use clippy_utils::{return_ty, trait_ref_of_method};
+use if_chain::if_chain;
+use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`.
+ ///
+ /// ### Why is this bad?
+ /// This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred.
+ ///
+ /// ### Example
+ /// ```rust
+ /// pub struct A;
+ ///
+ /// impl A {
+ /// pub fn to_string(&self) -> String {
+ /// "I am A".to_string()
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// pub struct A;
+ ///
+ /// impl fmt::Display for A {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "I am A")
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.38.0"]
+ pub INHERENT_TO_STRING,
+ style,
+ "type implements inherent method `to_string()`, but should instead implement the `Display` trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait.
+ ///
+ /// ### Why is this bad?
+ /// This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// pub struct A;
+ ///
+ /// impl A {
+ /// pub fn to_string(&self) -> String {
+ /// "I am A".to_string()
+ /// }
+ /// }
+ ///
+ /// impl fmt::Display for A {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "I am A, too")
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// pub struct A;
+ ///
+ /// impl fmt::Display for A {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "I am A")
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.38.0"]
+ pub INHERENT_TO_STRING_SHADOW_DISPLAY,
+ correctness,
+ "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait"
+}
+
+declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]);
+
+impl<'tcx> LateLintPass<'tcx> for InherentToString {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ if impl_item.span.from_expansion() {
+ return;
+ }
+
+ if_chain! {
+ // Check if item is a method, called to_string and has a parameter 'self'
+ if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
+ if impl_item.ident.name == sym::to_string;
+ let decl = &signature.decl;
+ if decl.implicit_self.has_implicit_self();
+ if decl.inputs.len() == 1;
+ if impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. }));
+
+ // Check if return type is String
+ if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::String);
+
+ // Filters instances of to_string which are required by a trait
+ if trait_ref_of_method(cx, impl_item.def_id.def_id).is_none();
+
+ then {
+ show_lint(cx, impl_item);
+ }
+ }
+ }
+}
+
+fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
- "type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
- self_type
++ let display_trait_id = cx
++ .tcx
++ .get_diagnostic_item(sym::Display)
++ .expect("Failed to get trait ID of `Display`!");
+
+ // Get the real type of 'self'
+ let self_type = cx.tcx.fn_sig(item.def_id).input(0);
+ let self_type = self_type.skip_binder().peel_refs();
+
+ // Emit either a warning or an error
+ if implements_trait(cx, self_type, display_trait_id, &[]) {
+ span_lint_and_help(
+ cx,
+ INHERENT_TO_STRING_SHADOW_DISPLAY,
+ item.span,
+ &format!(
- &format!("remove the inherent method from type `{}`", self_type),
++ "type `{self_type}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`"
+ ),
+ None,
- &format!(
- "implementation of inherent method `to_string(&self) -> String` for type `{}`",
- self_type
- ),
++ &format!("remove the inherent method from type `{self_type}`"),
+ );
+ } else {
+ span_lint_and_help(
+ cx,
+ INHERENT_TO_STRING,
+ item.span,
- &format!("implement trait `Display` for type `{}` instead", self_type),
++ &format!("implementation of inherent method `to_string(&self) -> String` for type `{self_type}`"),
+ None,
++ &format!("implement trait `Display` for type `{self_type}` instead"),
+ );
+ }
+}
--- /dev/null
- &format!("use of `#[inline]` on trait method `{}` which has no body", name),
+//! checks for `#[inline]` on trait methods without bodies
+
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::DiagnosticExt;
+use rustc_ast::ast::Attribute;
+use rustc_errors::Applicability;
+use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[inline]` on trait methods without bodies
+ ///
+ /// ### Why is this bad?
+ /// Only implementations of trait methods may be inlined.
+ /// The inline attribute is ignored for trait methods without bodies.
+ ///
+ /// ### Example
+ /// ```rust
+ /// trait Animal {
+ /// #[inline]
+ /// fn name(&self) -> &'static str;
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INLINE_FN_WITHOUT_BODY,
+ correctness,
+ "use of `#[inline]` on trait methods without bodies"
+}
+
+declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]);
+
+impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ check_attrs(cx, item.ident.name, attrs);
+ }
+ }
+}
+
+fn check_attrs(cx: &LateContext<'_>, name: Symbol, attrs: &[Attribute]) {
+ for attr in attrs {
+ if !attr.has_name(sym::inline) {
+ continue;
+ }
+
+ span_lint_and_then(
+ cx,
+ INLINE_FN_WITHOUT_BODY,
+ attr.span,
++ &format!("use of `#[inline]` on trait method `{name}` which has no body"),
+ |diag| {
+ diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable);
+ },
+ );
+ }
+}
--- /dev/null
- Side::Lhs => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)),
- Side::Rhs => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)),
+//! 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, Lit, LitKind};
+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(lit: &Lit, target_value: i128) -> bool {
+ if let LitKind::Int(value, ..) = lit.kind {
+ 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::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => {
+ match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
+ // `-1 + x`
+ (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ // `x - 1`
+ (BinOpKind::Sub, _, &ExprKind::Lit(ref 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::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs))
+ if rhskind.node == BinOpKind::Add =>
+ {
+ match (&rhslhs.kind, &rhsrhs.kind) {
+ // `y + 1` and `1 + y`
+ (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `x + 1 <= ...` or `1 + x <= ...`
+ (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _)
+ if lhskind.node == BinOpKind::Add =>
+ {
+ match (&lhslhs.kind, &lhsrhs.kind) {
+ // `1 + x` and `x + 1`
+ (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ (_, &ExprKind::Lit(ref 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::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => {
+ match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
+ // `-1 + y`
+ (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ // `y - 1`
+ (BinOpKind::Sub, _, &ExprKind::Lit(ref 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
- &format!(
- "this method is named `{}` but its return type does not implement `Iterator`",
- name
- ),
+use clippy_utils::{diagnostics::span_lint, get_parent_node, ty::implements_trait};
+use rustc_hir::{def_id::LocalDefId, FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind};
+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
+ /// Detects methods named `iter` or `iter_mut` that do not have a return type that implements `Iterator`.
+ ///
+ /// ### Why is this bad?
+ /// Methods named `iter` or `iter_mut` conventionally return an `Iterator`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // `String` does not implement `Iterator`
+ /// struct Data {}
+ /// impl Data {
+ /// fn iter(&self) -> String {
+ /// todo!()
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::str::Chars;
+ /// struct Data {}
+ /// impl Data {
+ /// fn iter(&self) -> Chars<'static> {
+ /// todo!()
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub ITER_NOT_RETURNING_ITERATOR,
+ pedantic,
+ "methods named `iter` or `iter_mut` that do not return an `Iterator`"
+}
+
+declare_lint_pass!(IterNotReturningIterator => [ITER_NOT_RETURNING_ITERATOR]);
+
+impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ let name = item.ident.name.as_str();
+ if matches!(name, "iter" | "iter_mut") {
+ if let TraitItemKind::Fn(fn_sig, _) = &item.kind {
+ check_sig(cx, name, fn_sig, item.def_id.def_id);
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>) {
+ let name = item.ident.name.as_str();
+ if matches!(name, "iter" | "iter_mut")
+ && !matches!(
+ get_parent_node(cx.tcx, item.hir_id()),
+ Some(Node::Item(Item { kind: ItemKind::Impl(i), .. })) if i.of_trait.is_some()
+ )
+ {
+ if let ImplItemKind::Fn(fn_sig, _) = &item.kind {
+ check_sig(cx, name, fn_sig, item.def_id.def_id);
+ }
+ }
+ }
+}
+
+fn check_sig(cx: &LateContext<'_>, name: &str, sig: &FnSig<'_>, fn_id: LocalDefId) {
+ if sig.decl.implicit_self.has_implicit_self() {
+ let ret_ty = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(fn_id).output());
+ let ret_ty = cx
+ .tcx
+ .try_normalize_erasing_regions(cx.param_env, ret_ty)
+ .unwrap_or(ret_ty);
+ if cx
+ .tcx
+ .get_diagnostic_item(sym::Iterator)
+ .map_or(false, |iter_id| !implements_trait(cx, ret_ty, iter_id, &[]))
+ {
+ span_lint(
+ cx,
+ ITER_NOT_RETURNING_ITERATOR,
+ sig.span,
++ &format!("this method is named `{name}` but its return type does not implement `Iterator`"),
+ );
+ }
+ }
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+use clippy_utils::diagnostics::span_lint_and_then;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, ConstKind};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{BytePos, Pos, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for large `const` arrays that should
+ /// be defined as `static` instead.
+ ///
+ /// ### Why is this bad?
+ /// Performance: const variables are inlined upon use.
+ /// Static items result in only one instance and has a fixed location in memory.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// pub const a = [0u32; 1_000_000];
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// pub static a = [0u32; 1_000_000];
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub LARGE_CONST_ARRAYS,
+ perf,
+ "large non-scalar const array may cause performance overhead"
+}
+
+pub struct LargeConstArrays {
+ maximum_allowed_size: u64,
+}
+
+impl LargeConstArrays {
+ #[must_use]
+ pub fn new(maximum_allowed_size: u64) -> Self {
+ Self { maximum_allowed_size }
+ }
+}
+
+impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
+
+impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if_chain! {
+ if !item.span.from_expansion();
+ if let ItemKind::Const(hir_ty, _) = &item.kind;
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let ty::Array(element_type, cst) = ty.kind();
+ if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind();
+ if let Ok(element_count) = element_count.try_to_machine_usize(cx.tcx);
+ if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes());
+ if self.maximum_allowed_size < element_count * element_size;
+
+ then {
+ let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
+ let sugg_span = Span::new(
+ hi_pos - BytePos::from_usize("const".len()),
+ hi_pos,
+ item.span.ctxt(),
+ item.span.parent(),
+ );
+ span_lint_and_then(
+ cx,
+ LARGE_CONST_ARRAYS,
+ item.span,
+ "large array defined as const",
+ |diag| {
+ diag.span_suggestion(
+ sugg_span,
+ "make this a static item",
+ "static",
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+ }
+ }
+}
--- /dev/null
- if cx.access_levels.is_exported(visited_trait.def_id.def_id) && trait_items.iter().any(|i| is_named_self(cx, i, sym::len))
+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.access_levels.is_exported(item.def_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.def_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.def_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);
+ }
+ }
+ }
+
- Self::Integral => format!("expected signature: `({}self) -> bool`", self_ref),
- Self::Option(_) => format!(
- "expected signature: `({}self) -> bool` or `({}self) -> Option<bool>",
- self_ref, self_ref
- ),
- Self::Result(..) => format!(
- "expected signature: `({}self) -> bool` or `({}self) -> Result<bool>",
- self_ref, self_ref
- ),
++ if cx.access_levels.is_exported(visited_trait.def_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.def_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 {
- "{} `{}` has a public `len` method, but no `is_empty` method",
- item_kind,
++ 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!(
- "{} `{}` has a public `len` method, but a private `is_empty` method",
- item_kind,
++ "{item_kind} `{}` has a public `len` method, but no `is_empty` method",
+ item_name.as_str(),
+ ),
+ None,
+ None,
+ ),
+ Some(is_empty) if !cx.access_levels.is_exported(is_empty.def_id.expect_local()) => (
+ format!(
- "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
- item_kind,
++ "{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!(
- &format!("using `{}is_empty` is clearer and more explicit", op),
++ "{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(ref 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" }),
- "{}{}.is_empty()",
- op,
++ &format!("using `{op}is_empty` is clearer and more explicit"),
+ format!(
- &format!("using `{}is_empty` is clearer and more explicit", op),
++ "{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",
- "{}{}.is_empty()",
- op,
++ &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
- "let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
- mut=mutability,
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{path_to_local_id, visitors::is_local_used};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{BindingAnnotation, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for variable declarations immediately followed by a
+ /// conditional affectation.
+ ///
+ /// ### Why is this bad?
+ /// This is not idiomatic Rust.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let foo;
+ ///
+ /// if bar() {
+ /// foo = 42;
+ /// } else {
+ /// foo = 0;
+ /// }
+ ///
+ /// let mut baz = None;
+ ///
+ /// if bar() {
+ /// baz = Some(42);
+ /// }
+ /// ```
+ ///
+ /// should be written
+ ///
+ /// ```rust,ignore
+ /// let foo = if bar() {
+ /// 42
+ /// } else {
+ /// 0
+ /// };
+ ///
+ /// let baz = if bar() {
+ /// Some(42)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_LET_IF_SEQ,
+ nursery,
+ "unidiomatic `let mut` declaration followed by initialization in `if`"
+}
+
+declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]);
+
+impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ let mut it = block.stmts.iter().peekable();
+ while let Some(stmt) = it.next() {
+ if_chain! {
+ if let Some(expr) = it.peek();
+ if let hir::StmtKind::Local(local) = stmt.kind;
+ if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
+ if let hir::StmtKind::Expr(if_) = expr.kind;
+ if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
+ if !is_local_used(cx, *cond, canonical_id);
+ if let hir::ExprKind::Block(then, _) = then.kind;
+ if let Some(value) = check_assign(cx, canonical_id, then);
+ if !is_local_used(cx, value, canonical_id);
+ then {
+ let span = stmt.span.to(if_.span);
+
+ let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze(
+ cx.tcx.at(span),
+ cx.param_env,
+ );
+ if has_interior_mutability { return; }
+
+ let (default_multi_stmts, default) = if let Some(else_) = else_ {
+ if let hir::ExprKind::Block(else_, _) = else_.kind {
+ if let Some(default) = check_assign(cx, canonical_id, else_) {
+ (else_.stmts.len() > 1, default)
+ } else if let Some(default) = local.init {
+ (true, default)
+ } else {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ } else if let Some(default) = local.init {
+ (false, default)
+ } else {
+ continue;
+ };
+
+ let mutability = match mode {
+ BindingAnnotation(_, Mutability::Mut) => "<mut> ",
+ _ => "",
+ };
+
+ // FIXME: this should not suggest `mut` if we can detect that the variable is not
+ // use mutably after the `if`
+
+ let sug = format!(
++ "let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
+ name=ident.name,
+ cond=snippet(cx, cond.span, "_"),
+ then=if then.stmts.len() > 1 { " ..;" } else { "" },
+ else=if default_multi_stmts { " ..;" } else { "" },
+ value=snippet(cx, value.span, "<value>"),
+ default=snippet(cx, default.span, "<default>"),
+ );
+ span_lint_and_then(cx,
+ USELESS_LET_IF_SEQ,
+ span,
+ "`if _ { .. } else { .. }` is an expression",
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "it is more idiomatic to write",
+ sug,
+ Applicability::HasPlaceholders,
+ );
+ if !mutability.is_empty() {
+ diag.note("you might not need `mut` at all");
+ }
+ });
+ }
+ }
+ }
+ }
+}
+
+fn check_assign<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: hir::HirId,
+ block: &'tcx hir::Block<'_>,
+) -> Option<&'tcx hir::Expr<'tcx>> {
+ if_chain! {
+ if block.expr.is_none();
+ if let Some(expr) = block.stmts.iter().last();
+ if let hir::StmtKind::Semi(expr) = expr.kind;
+ if let hir::ExprKind::Assign(var, value, _) = expr.kind;
+ if path_to_local_id(var, decl);
+ then {
+ if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
+ None
+ } else {
+ Some(value)
+ }
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
+// 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.
+
+store.register_group(true, "clippy::all", Some("clippy_all"), vec![
+ LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
+ LintId::of(approx_const::APPROX_CONSTANT),
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(attrs::DEPRECATED_SEMVER),
+ LintId::of(attrs::MISMATCHED_TARGET_OS),
+ LintId::of(attrs::USELESS_ATTRIBUTE),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(bool_to_int_with_if::BOOL_TO_INT_WITH_IF),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(booleans::OVERLY_COMPLEX_BOOL_EXPR),
+ LintId::of(borrow_deref_ref::BORROW_DEREF_REF),
++ LintId::of(box_default::BOX_DEFAULT),
+ LintId::of(casts::CAST_ABS_TO_UNSIGNED),
+ LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
+ LintId::of(casts::CAST_ENUM_TRUNCATION),
+ LintId::of(casts::CAST_REF_TO_MUT),
+ LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
+ LintId::of(casts::CAST_SLICE_FROM_RAW_PARTS),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(copies::IFS_SAME_COND),
+ LintId::of(copies::IF_SAME_THEN_ELSE),
+ LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
+ LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
+ LintId::of(dereference::EXPLICIT_AUTO_DEREF),
+ LintId::of(dereference::NEEDLESS_BORROW),
+ LintId::of(derivable_impls::DERIVABLE_IMPLS),
+ LintId::of(derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
++ LintId::of(disallowed_macros::DISALLOWED_MACROS),
+ LintId::of(disallowed_methods::DISALLOWED_METHODS),
+ LintId::of(disallowed_names::DISALLOWED_NAMES),
+ LintId::of(disallowed_types::DISALLOWED_TYPES),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(drop_forget_ref::DROP_COPY),
+ LintId::of(drop_forget_ref::DROP_NON_DROP),
+ LintId::of(drop_forget_ref::DROP_REF),
+ LintId::of(drop_forget_ref::FORGET_COPY),
+ LintId::of(drop_forget_ref::FORGET_NON_DROP),
+ LintId::of(drop_forget_ref::FORGET_REF),
+ LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
+ LintId::of(duplicate_mod::DUPLICATE_MOD),
+ LintId::of(entry::MAP_ENTRY),
+ LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(escape::BOXED_LOCAL),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
+ LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
+ LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
+ LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
+ LintId::of(formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(functions::RESULT_LARGE_ERR),
+ LintId::of(functions::RESULT_UNIT_ERR),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(if_let_mutex::IF_LET_MUTEX),
++ LintId::of(implicit_saturating_add::IMPLICIT_SATURATING_ADD),
+ LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(infinite_iter::INFINITE_ITER),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
+ LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
+ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::ITER_NEXT_LOOP),
+ LintId::of(loops::MANUAL_FIND),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::MANUAL_MEMCPY),
+ LintId::of(loops::MISSING_SPIN_LOOP),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(loops::NEEDLESS_COLLECT),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::NEVER_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_bits::MANUAL_BITS),
++ LintId::of(manual_clamp::MANUAL_CLAMP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
+ LintId::of(manual_retain::MANUAL_RETAIN),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(match_result_ok::MATCH_RESULT_OK),
+ LintId::of(matches::COLLAPSIBLE_MATCH),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MANUAL_MAP),
+ LintId::of(matches::MANUAL_UNWRAP_OR),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::MATCH_STR_CASE_MISMATCH),
+ LintId::of(matches::NEEDLESS_MATCH),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::BYTES_COUNT_TO_LEN),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::CLONE_DOUBLE_REF),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::COLLAPSIBLE_STR_REPLACE),
+ LintId::of(methods::ERR_EXPECT),
+ LintId::of(methods::EXPECT_FUN_CALL),
+ LintId::of(methods::EXTEND_WITH_DRAIN),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::GET_FIRST),
+ LintId::of(methods::GET_LAST_WITH_LEN),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::IS_DIGIT_ASCII_RADIX),
+ LintId::of(methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::ITER_KV_MAP),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_OVEREAGER_CLONED),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MANUAL_SPLIT_ONCE),
+ LintId::of(methods::MANUAL_STR_REPEAT),
+ LintId::of(methods::MAP_CLONE),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_IDENTITY),
+ LintId::of(methods::MUT_MUTEX_LOCK),
+ LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
+ LintId::of(methods::NEEDLESS_OPTION_TAKE),
+ LintId::of(methods::NEEDLESS_SPLITN),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(methods::NO_EFFECT_REPLACE),
+ LintId::of(methods::OBFUSCATED_IF_ELSE),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::OR_THEN_UNWRAP),
+ LintId::of(methods::RANGE_ZIP_WITH_LEN),
+ LintId::of(methods::REPEAT_ONCE),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::SINGLE_CHAR_PATTERN),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(methods::SUSPICIOUS_SPLITN),
+ LintId::of(methods::SUSPICIOUS_TO_OWNED),
+ LintId::of(methods::UNINIT_ASSUMED_INIT),
+ LintId::of(methods::UNIT_HASH),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::UNNECESSARY_FIND_MAP),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::UNNECESSARY_SORT_BY),
+ LintId::of(methods::UNNECESSARY_TO_OWNED),
+ LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(methods::VEC_RESIZE_TO_ZERO),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(methods::ZST_OFFSET),
+ LintId::of(minmax::MIN_MAX),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION),
+ LintId::of(multi_assignments::MULTI_ASSIGNMENTS),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
+ LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS),
+ LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
+ LintId::of(needless_update::NEEDLESS_UPDATE),
+ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
+ LintId::of(octal_escapes::OCTAL_ESCAPES),
+ LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
+ LintId::of(operators::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(operators::ASSIGN_OP_PATTERN),
+ LintId::of(operators::BAD_BIT_MASK),
+ LintId::of(operators::CMP_NAN),
+ LintId::of(operators::CMP_OWNED),
+ LintId::of(operators::DOUBLE_COMPARISONS),
+ LintId::of(operators::DURATION_SUBSEC),
+ LintId::of(operators::EQ_OP),
+ LintId::of(operators::ERASING_OP),
+ LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(operators::IDENTITY_OP),
+ LintId::of(operators::INEFFECTIVE_BIT_MASK),
+ LintId::of(operators::MISREFACTORED_ASSIGN_OP),
+ LintId::of(operators::MODULO_ONE),
+ LintId::of(operators::OP_REF),
+ LintId::of(operators::PTR_EQ),
+ LintId::of(operators::SELF_ASSIGNMENT),
+ LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(partialeq_to_none::PARTIALEQ_TO_NONE),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::INVALID_NULL_PTR_USAGE),
+ LintId::of(ptr::MUT_FROM_REF),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(ranges::REVERSED_EMPTY_RANGES),
+ LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
+ LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC),
+ LintId::of(redundant_clone::REDUNDANT_CLONE),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(regex::INVALID_REGEX),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
+ LintId::of(serde_api::SERDE_API_MISUSE),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
+ LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(strings::TRIM_SPLIT_WHITESPACE),
+ LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(swap::ALMOST_SWAPPED),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(transmute::TRANSMUTING_NULL),
+ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
+ LintId::of(transmute::USELESS_TRANSMUTE),
+ LintId::of(transmute::WRONG_TRANSMUTE),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::BOX_COLLECTION),
+ LintId::of(types::REDUNDANT_ALLOCATION),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(unicode::INVISIBLE_CHARACTERS),
+ LintId::of(uninit_vec::UNINIT_VEC),
+ LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
+ LintId::of(unit_types::LET_UNIT_VALUE),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unit_types::UNIT_CMP),
+ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(unwrap::PANICKING_UNWRAP),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(vec::USELESS_VEC),
+ LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+])
--- /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.
+
+store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(borrow_deref_ref::BORROW_DEREF_REF),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(dereference::EXPLICIT_AUTO_DEREF),
+ LintId::of(derivable_impls::DERIVABLE_IMPLS),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::MANUAL_FIND),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_LET_LOOP),
++ LintId::of(manual_clamp::MANUAL_CLAMP),
+ LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(matches::MANUAL_UNWRAP_OR),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::NEEDLESS_MATCH),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::BYTES_COUNT_TO_LEN),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::GET_LAST_WITH_LEN),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::ITER_KV_MAP),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SPLIT_ONCE),
+ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_IDENTITY),
+ LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
+ LintId::of(methods::NEEDLESS_OPTION_TAKE),
+ LintId::of(methods::NEEDLESS_SPLITN),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OR_THEN_UNWRAP),
+ LintId::of(methods::RANGE_ZIP_WITH_LEN),
+ LintId::of(methods::REPEAT_ONCE),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::UNNECESSARY_FIND_MAP),
+ LintId::of(methods::UNNECESSARY_SORT_BY),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
+ LintId::of(needless_update::NEEDLESS_UPDATE),
+ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
+ LintId::of(operators::DOUBLE_COMPARISONS),
+ LintId::of(operators::DURATION_SUBSEC),
+ LintId::of(operators::IDENTITY_OP),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(transmute::USELESS_TRANSMUTE),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+])
--- /dev/null
- LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
+// 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.
+
+store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
+ LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL),
+ LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
+ LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS),
+ LintId::of(utils::internal_lints::DEFAULT_DEPRECATION_REASON),
+ LintId::of(utils::internal_lints::DEFAULT_LINT),
+ LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
+ LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
+ LintId::of(utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE),
+ LintId::of(utils::internal_lints::INVALID_PATHS),
+ LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
+ LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
+ LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL),
+ LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
+ LintId::of(utils::internal_lints::PRODUCE_ICE),
++ LintId::of(utils::internal_lints::UNNECESSARY_DEF_PATH),
+ LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),
+])
--- /dev/null
- utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
- #[cfg(feature = "internal")]
+// 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.
+
+store.register_lints(&[
+ #[cfg(feature = "internal")]
+ utils::internal_lints::CLIPPY_LINTS_INTERNAL,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::COMPILER_LINT_FUNCTIONS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::DEFAULT_DEPRECATION_REASON,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::DEFAULT_LINT,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::IF_CHAIN_STYLE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::INTERNING_DEFINED_SYMBOL,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::INVALID_PATHS,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::LINT_WITHOUT_LINT_PASS,
+ #[cfg(feature = "internal")]
- doc_link_with_quotes::DOC_LINK_WITH_QUOTES,
+ utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::OUTER_EXPN_EXPN_DATA,
+ #[cfg(feature = "internal")]
+ utils::internal_lints::PRODUCE_ICE,
+ #[cfg(feature = "internal")]
++ utils::internal_lints::UNNECESSARY_DEF_PATH,
++ #[cfg(feature = "internal")]
+ utils::internal_lints::UNNECESSARY_SYMBOL_STR,
+ almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
+ approx_const::APPROX_CONSTANT,
+ as_conversions::AS_CONVERSIONS,
+ asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
+ asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
+ assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
+ assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES,
+ async_yields_async::ASYNC_YIELDS_ASYNC,
+ attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
+ attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
+ attrs::DEPRECATED_CFG_ATTR,
+ attrs::DEPRECATED_SEMVER,
+ attrs::EMPTY_LINE_AFTER_OUTER_ATTR,
+ attrs::INLINE_ALWAYS,
+ attrs::MISMATCHED_TARGET_OS,
+ attrs::USELESS_ATTRIBUTE,
+ await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE,
+ await_holding_invalid::AWAIT_HOLDING_LOCK,
+ await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
+ blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
+ bool_assert_comparison::BOOL_ASSERT_COMPARISON,
+ bool_to_int_with_if::BOOL_TO_INT_WITH_IF,
+ booleans::NONMINIMAL_BOOL,
+ booleans::OVERLY_COMPLEX_BOOL_EXPR,
+ borrow_deref_ref::BORROW_DEREF_REF,
++ box_default::BOX_DEFAULT,
+ cargo::CARGO_COMMON_METADATA,
+ cargo::MULTIPLE_CRATE_VERSIONS,
+ cargo::NEGATIVE_FEATURE_NAMES,
+ cargo::REDUNDANT_FEATURE_NAMES,
+ cargo::WILDCARD_DEPENDENCIES,
+ casts::AS_UNDERSCORE,
+ casts::BORROW_AS_PTR,
+ casts::CAST_ABS_TO_UNSIGNED,
+ casts::CAST_ENUM_CONSTRUCTOR,
+ casts::CAST_ENUM_TRUNCATION,
+ casts::CAST_LOSSLESS,
+ casts::CAST_POSSIBLE_TRUNCATION,
+ casts::CAST_POSSIBLE_WRAP,
+ casts::CAST_PRECISION_LOSS,
+ casts::CAST_PTR_ALIGNMENT,
+ casts::CAST_REF_TO_MUT,
+ casts::CAST_SIGN_LOSS,
+ casts::CAST_SLICE_DIFFERENT_SIZES,
+ casts::CAST_SLICE_FROM_RAW_PARTS,
+ casts::CHAR_LIT_AS_U8,
+ casts::FN_TO_NUMERIC_CAST,
+ casts::FN_TO_NUMERIC_CAST_ANY,
+ casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ casts::PTR_AS_PTR,
+ casts::UNNECESSARY_CAST,
+ checked_conversions::CHECKED_CONVERSIONS,
+ cognitive_complexity::COGNITIVE_COMPLEXITY,
+ collapsible_if::COLLAPSIBLE_ELSE_IF,
+ collapsible_if::COLLAPSIBLE_IF,
+ comparison_chain::COMPARISON_CHAIN,
+ copies::BRANCHES_SHARING_CODE,
+ copies::IFS_SAME_COND,
+ copies::IF_SAME_THEN_ELSE,
+ copies::SAME_FUNCTIONS_IN_IF_CONDITION,
+ copy_iterator::COPY_ITERATOR,
+ crate_in_macro_def::CRATE_IN_MACRO_DEF,
+ create_dir::CREATE_DIR,
+ dbg_macro::DBG_MACRO,
+ default::DEFAULT_TRAIT_ACCESS,
+ default::FIELD_REASSIGN_WITH_DEFAULT,
+ default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY,
+ default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
+ default_union_representation::DEFAULT_UNION_REPRESENTATION,
+ dereference::EXPLICIT_AUTO_DEREF,
+ dereference::EXPLICIT_DEREF_METHODS,
+ dereference::NEEDLESS_BORROW,
+ dereference::REF_BINDING_TO_REFERENCE,
+ derivable_impls::DERIVABLE_IMPLS,
+ derive::DERIVE_HASH_XOR_EQ,
+ derive::DERIVE_ORD_XOR_PARTIAL_ORD,
+ derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ,
+ derive::EXPL_IMPL_CLONE_ON_COPY,
+ derive::UNSAFE_DERIVE_DESERIALIZE,
++ disallowed_macros::DISALLOWED_MACROS,
+ disallowed_methods::DISALLOWED_METHODS,
+ disallowed_names::DISALLOWED_NAMES,
+ disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS,
+ disallowed_types::DISALLOWED_TYPES,
++ doc::DOC_LINK_WITH_QUOTES,
+ doc::DOC_MARKDOWN,
+ doc::MISSING_ERRORS_DOC,
+ doc::MISSING_PANICS_DOC,
+ doc::MISSING_SAFETY_DOC,
+ doc::NEEDLESS_DOCTEST_MAIN,
+ double_parens::DOUBLE_PARENS,
+ drop_forget_ref::DROP_COPY,
+ drop_forget_ref::DROP_NON_DROP,
+ drop_forget_ref::DROP_REF,
+ drop_forget_ref::FORGET_COPY,
+ drop_forget_ref::FORGET_NON_DROP,
+ drop_forget_ref::FORGET_REF,
+ drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
+ duplicate_mod::DUPLICATE_MOD,
+ else_if_without_else::ELSE_IF_WITHOUT_ELSE,
+ empty_drop::EMPTY_DROP,
+ empty_enum::EMPTY_ENUM,
+ empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS,
+ entry::MAP_ENTRY,
+ enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
+ enum_variants::ENUM_VARIANT_NAMES,
+ enum_variants::MODULE_INCEPTION,
+ enum_variants::MODULE_NAME_REPETITIONS,
+ equatable_if_let::EQUATABLE_IF_LET,
+ escape::BOXED_LOCAL,
+ eta_reduction::REDUNDANT_CLOSURE,
+ eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
+ excessive_bools::STRUCT_EXCESSIVE_BOOLS,
+ exhaustive_items::EXHAUSTIVE_ENUMS,
+ exhaustive_items::EXHAUSTIVE_STRUCTS,
+ exit::EXIT,
+ explicit_write::EXPLICIT_WRITE,
+ fallible_impl_from::FALLIBLE_IMPL_FROM,
+ float_literal::EXCESSIVE_PRECISION,
+ float_literal::LOSSY_FLOAT_LITERAL,
+ floating_point_arithmetic::IMPRECISE_FLOPS,
+ floating_point_arithmetic::SUBOPTIMAL_FLOPS,
+ format::USELESS_FORMAT,
+ format_args::FORMAT_IN_FORMAT_ARGS,
+ format_args::TO_STRING_IN_FORMAT_ARGS,
++ format_args::UNINLINED_FORMAT_ARGS,
+ format_impl::PRINT_IN_FORMAT_IMPL,
+ format_impl::RECURSIVE_FORMAT_IMPL,
+ format_push_string::FORMAT_PUSH_STRING,
+ formatting::POSSIBLE_MISSING_COMMA,
+ formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ formatting::SUSPICIOUS_ELSE_FORMATTING,
+ formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
+ from_over_into::FROM_OVER_INTO,
+ from_str_radix_10::FROM_STR_RADIX_10,
+ functions::DOUBLE_MUST_USE,
+ functions::MUST_USE_CANDIDATE,
+ functions::MUST_USE_UNIT,
+ functions::NOT_UNSAFE_PTR_ARG_DEREF,
+ functions::RESULT_LARGE_ERR,
+ functions::RESULT_UNIT_ERR,
+ functions::TOO_MANY_ARGUMENTS,
+ functions::TOO_MANY_LINES,
+ future_not_send::FUTURE_NOT_SEND,
+ if_let_mutex::IF_LET_MUTEX,
+ if_not_else::IF_NOT_ELSE,
+ if_then_some_else_none::IF_THEN_SOME_ELSE_NONE,
+ implicit_hasher::IMPLICIT_HASHER,
+ implicit_return::IMPLICIT_RETURN,
++ implicit_saturating_add::IMPLICIT_SATURATING_ADD,
+ implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
+ inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
+ index_refutable_slice::INDEX_REFUTABLE_SLICE,
+ indexing_slicing::INDEXING_SLICING,
+ indexing_slicing::OUT_OF_BOUNDS_INDEXING,
+ infinite_iter::INFINITE_ITER,
+ infinite_iter::MAYBE_INFINITE_ITER,
+ inherent_impl::MULTIPLE_INHERENT_IMPL,
+ inherent_to_string::INHERENT_TO_STRING,
+ inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
+ init_numbered_fields::INIT_NUMBERED_FIELDS,
+ inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
+ int_plus_one::INT_PLUS_ONE,
+ invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
+ invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED,
+ items_after_statements::ITEMS_AFTER_STATEMENTS,
+ iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
+ large_const_arrays::LARGE_CONST_ARRAYS,
+ large_enum_variant::LARGE_ENUM_VARIANT,
+ large_include_file::LARGE_INCLUDE_FILE,
+ large_stack_arrays::LARGE_STACK_ARRAYS,
+ len_zero::COMPARISON_TO_EMPTY,
+ len_zero::LEN_WITHOUT_IS_EMPTY,
+ len_zero::LEN_ZERO,
+ let_if_seq::USELESS_LET_IF_SEQ,
+ let_underscore::LET_UNDERSCORE_DROP,
+ let_underscore::LET_UNDERSCORE_LOCK,
+ let_underscore::LET_UNDERSCORE_MUST_USE,
+ lifetimes::EXTRA_UNUSED_LIFETIMES,
+ lifetimes::NEEDLESS_LIFETIMES,
+ literal_representation::DECIMAL_LITERAL_REPRESENTATION,
+ literal_representation::INCONSISTENT_DIGIT_GROUPING,
+ literal_representation::LARGE_DIGIT_GROUPS,
+ literal_representation::MISTYPED_LITERAL_SUFFIXES,
+ literal_representation::UNREADABLE_LITERAL,
+ literal_representation::UNUSUAL_BYTE_GROUPINGS,
+ loops::EMPTY_LOOP,
+ loops::EXPLICIT_COUNTER_LOOP,
+ loops::EXPLICIT_INTO_ITER_LOOP,
+ loops::EXPLICIT_ITER_LOOP,
+ loops::FOR_KV_MAP,
+ loops::FOR_LOOPS_OVER_FALLIBLES,
+ loops::ITER_NEXT_LOOP,
+ loops::MANUAL_FIND,
+ loops::MANUAL_FLATTEN,
+ loops::MANUAL_MEMCPY,
+ loops::MISSING_SPIN_LOOP,
+ loops::MUT_RANGE_BOUND,
+ loops::NEEDLESS_COLLECT,
+ loops::NEEDLESS_RANGE_LOOP,
+ loops::NEVER_LOOP,
+ loops::SAME_ITEM_PUSH,
+ loops::SINGLE_ELEMENT_LOOP,
+ loops::WHILE_IMMUTABLE_CONDITION,
+ loops::WHILE_LET_LOOP,
+ loops::WHILE_LET_ON_ITERATOR,
+ macro_use::MACRO_USE_IMPORTS,
+ main_recursion::MAIN_RECURSION,
+ manual_assert::MANUAL_ASSERT,
+ manual_async_fn::MANUAL_ASYNC_FN,
+ manual_bits::MANUAL_BITS,
++ manual_clamp::MANUAL_CLAMP,
+ manual_instant_elapsed::MANUAL_INSTANT_ELAPSED,
+ manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
+ manual_rem_euclid::MANUAL_REM_EUCLID,
+ manual_retain::MANUAL_RETAIN,
+ manual_string_new::MANUAL_STRING_NEW,
+ manual_strip::MANUAL_STRIP,
+ map_unit_fn::OPTION_MAP_UNIT_FN,
+ map_unit_fn::RESULT_MAP_UNIT_FN,
+ match_result_ok::MATCH_RESULT_OK,
+ matches::COLLAPSIBLE_MATCH,
+ matches::INFALLIBLE_DESTRUCTURING_MATCH,
+ matches::MANUAL_MAP,
+ matches::MANUAL_UNWRAP_OR,
+ matches::MATCH_AS_REF,
+ matches::MATCH_BOOL,
+ matches::MATCH_LIKE_MATCHES_MACRO,
+ matches::MATCH_ON_VEC_ITEMS,
+ matches::MATCH_OVERLAPPING_ARM,
+ matches::MATCH_REF_PATS,
+ matches::MATCH_SAME_ARMS,
+ matches::MATCH_SINGLE_BINDING,
+ matches::MATCH_STR_CASE_MISMATCH,
+ matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ matches::MATCH_WILD_ERR_ARM,
+ matches::NEEDLESS_MATCH,
+ matches::REDUNDANT_PATTERN_MATCHING,
+ matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ matches::SIGNIFICANT_DROP_IN_SCRUTINEE,
+ matches::SINGLE_MATCH,
+ matches::SINGLE_MATCH_ELSE,
+ matches::TRY_ERR,
+ matches::WILDCARD_ENUM_MATCH_ARM,
+ matches::WILDCARD_IN_OR_PATTERNS,
+ mem_forget::MEM_FORGET,
+ mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
+ mem_replace::MEM_REPLACE_WITH_DEFAULT,
+ mem_replace::MEM_REPLACE_WITH_UNINIT,
+ methods::BIND_INSTEAD_OF_MAP,
+ methods::BYTES_COUNT_TO_LEN,
+ methods::BYTES_NTH,
+ methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ methods::CHARS_LAST_CMP,
+ methods::CHARS_NEXT_CMP,
+ methods::CLONED_INSTEAD_OF_COPIED,
+ methods::CLONE_DOUBLE_REF,
+ methods::CLONE_ON_COPY,
+ methods::CLONE_ON_REF_PTR,
+ methods::COLLAPSIBLE_STR_REPLACE,
+ methods::ERR_EXPECT,
+ methods::EXPECT_FUN_CALL,
+ methods::EXPECT_USED,
+ methods::EXTEND_WITH_DRAIN,
+ methods::FILETYPE_IS_FILE,
+ methods::FILTER_MAP_IDENTITY,
+ methods::FILTER_MAP_NEXT,
+ methods::FILTER_NEXT,
+ methods::FLAT_MAP_IDENTITY,
+ methods::FLAT_MAP_OPTION,
+ methods::FROM_ITER_INSTEAD_OF_COLLECT,
+ methods::GET_FIRST,
+ methods::GET_LAST_WITH_LEN,
+ methods::GET_UNWRAP,
+ methods::IMPLICIT_CLONE,
+ methods::INEFFICIENT_TO_STRING,
+ methods::INSPECT_FOR_EACH,
+ methods::INTO_ITER_ON_REF,
+ methods::IS_DIGIT_ASCII_RADIX,
+ methods::ITERATOR_STEP_BY_ZERO,
+ methods::ITER_CLONED_COLLECT,
+ methods::ITER_COUNT,
+ methods::ITER_KV_MAP,
+ methods::ITER_NEXT_SLICE,
+ methods::ITER_NTH,
+ methods::ITER_NTH_ZERO,
+ methods::ITER_ON_EMPTY_COLLECTIONS,
+ methods::ITER_ON_SINGLE_ITEMS,
+ methods::ITER_OVEREAGER_CLONED,
+ methods::ITER_SKIP_NEXT,
+ methods::ITER_WITH_DRAIN,
+ methods::MANUAL_FILTER_MAP,
+ methods::MANUAL_FIND_MAP,
+ methods::MANUAL_OK_OR,
+ methods::MANUAL_SATURATING_ARITHMETIC,
+ methods::MANUAL_SPLIT_ONCE,
+ methods::MANUAL_STR_REPEAT,
+ methods::MAP_CLONE,
+ methods::MAP_COLLECT_RESULT_UNIT,
+ methods::MAP_ERR_IGNORE,
+ methods::MAP_FLATTEN,
+ methods::MAP_IDENTITY,
+ methods::MAP_UNWRAP_OR,
+ methods::MUT_MUTEX_LOCK,
+ methods::NAIVE_BYTECOUNT,
+ methods::NEEDLESS_OPTION_AS_DEREF,
+ methods::NEEDLESS_OPTION_TAKE,
+ methods::NEEDLESS_SPLITN,
+ methods::NEW_RET_NO_SELF,
+ methods::NONSENSICAL_OPEN_OPTIONS,
+ methods::NO_EFFECT_REPLACE,
+ methods::OBFUSCATED_IF_ELSE,
+ methods::OK_EXPECT,
+ methods::OPTION_AS_REF_DEREF,
+ methods::OPTION_FILTER_MAP,
+ methods::OPTION_MAP_OR_NONE,
+ methods::OR_FUN_CALL,
+ methods::OR_THEN_UNWRAP,
+ methods::PATH_BUF_PUSH_OVERWRITE,
+ methods::RANGE_ZIP_WITH_LEN,
+ methods::REPEAT_ONCE,
+ methods::RESULT_MAP_OR_INTO_OPTION,
+ methods::SEARCH_IS_SOME,
+ methods::SHOULD_IMPLEMENT_TRAIT,
+ methods::SINGLE_CHAR_ADD_STR,
+ methods::SINGLE_CHAR_PATTERN,
+ methods::SKIP_WHILE_NEXT,
+ methods::STABLE_SORT_PRIMITIVE,
+ methods::STRING_EXTEND_CHARS,
+ methods::SUSPICIOUS_MAP,
+ methods::SUSPICIOUS_SPLITN,
+ methods::SUSPICIOUS_TO_OWNED,
+ methods::UNINIT_ASSUMED_INIT,
+ methods::UNIT_HASH,
+ methods::UNNECESSARY_FILTER_MAP,
+ methods::UNNECESSARY_FIND_MAP,
+ methods::UNNECESSARY_FOLD,
+ methods::UNNECESSARY_JOIN,
+ methods::UNNECESSARY_LAZY_EVALUATIONS,
+ methods::UNNECESSARY_SORT_BY,
+ methods::UNNECESSARY_TO_OWNED,
+ methods::UNWRAP_OR_ELSE_DEFAULT,
+ methods::UNWRAP_USED,
+ methods::USELESS_ASREF,
+ methods::VEC_RESIZE_TO_ZERO,
+ methods::VERBOSE_FILE_READS,
+ methods::WRONG_SELF_CONVENTION,
+ methods::ZST_OFFSET,
+ minmax::MIN_MAX,
+ misc::SHORT_CIRCUIT_STATEMENT,
+ misc::TOPLEVEL_REF_ARG,
+ misc::USED_UNDERSCORE_BINDING,
+ misc::ZERO_PTR,
+ misc_early::BUILTIN_TYPE_SHADOW,
+ misc_early::DOUBLE_NEG,
+ misc_early::DUPLICATE_UNDERSCORE_ARGUMENT,
+ misc_early::MIXED_CASE_HEX_LITERALS,
+ misc_early::REDUNDANT_PATTERN,
+ misc_early::SEPARATED_LITERAL_SUFFIX,
+ misc_early::UNNEEDED_FIELD_PATTERN,
+ misc_early::UNNEEDED_WILDCARD_PATTERN,
+ misc_early::UNSEPARATED_LITERAL_SUFFIX,
+ misc_early::ZERO_PREFIXED_LITERAL,
+ mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER,
+ missing_const_for_fn::MISSING_CONST_FOR_FN,
+ missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
+ missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
+ missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
+ mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION,
+ mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION,
+ module_style::MOD_MODULE_FILES,
+ module_style::SELF_NAMED_MODULE_FILES,
+ multi_assignments::MULTI_ASSIGNMENTS,
+ mut_key::MUTABLE_KEY_TYPE,
+ mut_mut::MUT_MUT,
+ mut_reference::UNNECESSARY_MUT_PASSED,
+ mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
+ mutex_atomic::MUTEX_ATOMIC,
+ mutex_atomic::MUTEX_INTEGER,
+ needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE,
+ needless_bool::BOOL_COMPARISON,
+ needless_bool::NEEDLESS_BOOL,
+ needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
+ needless_continue::NEEDLESS_CONTINUE,
+ needless_for_each::NEEDLESS_FOR_EACH,
+ needless_late_init::NEEDLESS_LATE_INIT,
+ needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS,
+ needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
+ needless_question_mark::NEEDLESS_QUESTION_MARK,
+ needless_update::NEEDLESS_UPDATE,
+ neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
+ neg_multiply::NEG_MULTIPLY,
+ new_without_default::NEW_WITHOUT_DEFAULT,
+ no_effect::NO_EFFECT,
+ no_effect::NO_EFFECT_UNDERSCORE_BINDING,
+ no_effect::UNNECESSARY_OPERATION,
+ non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
+ non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
+ non_expressive_names::JUST_UNDERSCORES_AND_DIGITS,
+ non_expressive_names::MANY_SINGLE_CHAR_NAMES,
+ non_expressive_names::SIMILAR_NAMES,
+ non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
+ non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
+ nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
+ octal_escapes::OCTAL_ESCAPES,
+ only_used_in_recursion::ONLY_USED_IN_RECURSION,
+ operators::ABSURD_EXTREME_COMPARISONS,
+ operators::ARITHMETIC_SIDE_EFFECTS,
+ operators::ASSIGN_OP_PATTERN,
+ operators::BAD_BIT_MASK,
+ operators::CMP_NAN,
+ operators::CMP_OWNED,
+ operators::DOUBLE_COMPARISONS,
+ operators::DURATION_SUBSEC,
+ operators::EQ_OP,
+ operators::ERASING_OP,
+ operators::FLOAT_ARITHMETIC,
+ operators::FLOAT_CMP,
+ operators::FLOAT_CMP_CONST,
+ operators::FLOAT_EQUALITY_WITHOUT_ABS,
+ operators::IDENTITY_OP,
+ operators::INEFFECTIVE_BIT_MASK,
+ operators::INTEGER_ARITHMETIC,
+ operators::INTEGER_DIVISION,
+ operators::MISREFACTORED_ASSIGN_OP,
+ operators::MODULO_ARITHMETIC,
+ operators::MODULO_ONE,
+ operators::NEEDLESS_BITWISE_BOOL,
+ operators::OP_REF,
+ operators::PTR_EQ,
+ operators::SELF_ASSIGNMENT,
+ operators::VERBOSE_BIT_MASK,
+ option_env_unwrap::OPTION_ENV_UNWRAP,
+ option_if_let_else::OPTION_IF_LET_ELSE,
+ overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
+ panic_in_result_fn::PANIC_IN_RESULT_FN,
+ panic_unimplemented::PANIC,
+ panic_unimplemented::TODO,
+ panic_unimplemented::UNIMPLEMENTED,
+ panic_unimplemented::UNREACHABLE,
+ partialeq_ne_impl::PARTIALEQ_NE_IMPL,
+ partialeq_to_none::PARTIALEQ_TO_NONE,
+ pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE,
+ pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF,
+ pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
+ precedence::PRECEDENCE,
+ ptr::CMP_NULL,
+ ptr::INVALID_NULL_PTR_USAGE,
+ ptr::MUT_FROM_REF,
+ ptr::PTR_ARG,
+ ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
+ pub_use::PUB_USE,
+ question_mark::QUESTION_MARK,
+ ranges::MANUAL_RANGE_CONTAINS,
+ ranges::RANGE_MINUS_ONE,
+ ranges::RANGE_PLUS_ONE,
+ ranges::REVERSED_EMPTY_RANGES,
+ rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT,
+ read_zero_byte_vec::READ_ZERO_BYTE_VEC,
+ redundant_clone::REDUNDANT_CLONE,
+ redundant_closure_call::REDUNDANT_CLOSURE_CALL,
+ redundant_else::REDUNDANT_ELSE,
+ redundant_field_names::REDUNDANT_FIELD_NAMES,
+ redundant_pub_crate::REDUNDANT_PUB_CRATE,
+ redundant_slicing::DEREF_BY_SLICING,
+ redundant_slicing::REDUNDANT_SLICING,
+ redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
+ ref_option_ref::REF_OPTION_REF,
+ reference::DEREF_ADDROF,
+ regex::INVALID_REGEX,
+ regex::TRIVIAL_REGEX,
+ return_self_not_must_use::RETURN_SELF_NOT_MUST_USE,
+ returns::LET_AND_RETURN,
+ returns::NEEDLESS_RETURN,
+ same_name_method::SAME_NAME_METHOD,
+ self_named_constructors::SELF_NAMED_CONSTRUCTORS,
+ semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
+ serde_api::SERDE_API_MISUSE,
+ shadow::SHADOW_REUSE,
+ shadow::SHADOW_SAME,
+ shadow::SHADOW_UNRELATED,
+ single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES,
+ single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
+ size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
+ slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
+ std_instead_of_core::ALLOC_INSTEAD_OF_CORE,
+ std_instead_of_core::STD_INSTEAD_OF_ALLOC,
+ std_instead_of_core::STD_INSTEAD_OF_CORE,
+ strings::STRING_ADD,
+ strings::STRING_ADD_ASSIGN,
+ strings::STRING_FROM_UTF8_AS_BYTES,
+ strings::STRING_LIT_AS_BYTES,
+ strings::STRING_SLICE,
+ strings::STRING_TO_STRING,
+ strings::STR_TO_STRING,
+ strings::TRIM_SPLIT_WHITESPACE,
+ strlen_on_c_strings::STRLEN_ON_C_STRINGS,
+ suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS,
+ suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
+ suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
+ swap::ALMOST_SWAPPED,
+ swap::MANUAL_SWAP,
+ swap_ptr_to_ref::SWAP_PTR_TO_REF,
+ tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
+ temporary_assignment::TEMPORARY_ASSIGNMENT,
+ to_digit_is_some::TO_DIGIT_IS_SOME,
+ trailing_empty_array::TRAILING_EMPTY_ARRAY,
+ trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
+ trait_bounds::TYPE_REPETITION_IN_BOUNDS,
+ transmute::CROSSPOINTER_TRANSMUTE,
+ transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ transmute::TRANSMUTE_BYTES_TO_STR,
+ transmute::TRANSMUTE_FLOAT_TO_INT,
+ transmute::TRANSMUTE_INT_TO_BOOL,
+ transmute::TRANSMUTE_INT_TO_CHAR,
+ transmute::TRANSMUTE_INT_TO_FLOAT,
+ transmute::TRANSMUTE_NUM_TO_BYTES,
+ transmute::TRANSMUTE_PTR_TO_PTR,
+ transmute::TRANSMUTE_PTR_TO_REF,
+ transmute::TRANSMUTE_UNDEFINED_REPR,
+ transmute::TRANSMUTING_NULL,
+ transmute::UNSOUND_COLLECTION_TRANSMUTE,
+ transmute::USELESS_TRANSMUTE,
+ transmute::WRONG_TRANSMUTE,
+ types::BORROWED_BOX,
+ types::BOX_COLLECTION,
+ types::LINKEDLIST,
+ types::OPTION_OPTION,
+ types::RC_BUFFER,
+ types::RC_MUTEX,
+ types::REDUNDANT_ALLOCATION,
+ types::TYPE_COMPLEXITY,
+ types::VEC_BOX,
+ undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
+ unicode::INVISIBLE_CHARACTERS,
+ unicode::NON_ASCII_LITERAL,
+ unicode::UNICODE_NOT_NFC,
+ uninit_vec::UNINIT_VEC,
+ unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
+ unit_types::LET_UNIT_VALUE,
+ unit_types::UNIT_ARG,
+ unit_types::UNIT_CMP,
+ unnamed_address::FN_ADDRESS_COMPARISONS,
+ unnamed_address::VTABLE_ADDRESS_COMPARISONS,
+ unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS,
+ unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS,
+ unnecessary_wraps::UNNECESSARY_WRAPS,
+ unnested_or_patterns::UNNESTED_OR_PATTERNS,
+ unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
+ unused_async::UNUSED_ASYNC,
+ unused_io_amount::UNUSED_IO_AMOUNT,
+ unused_peekable::UNUSED_PEEKABLE,
+ unused_rounding::UNUSED_ROUNDING,
+ unused_self::UNUSED_SELF,
+ unused_unit::UNUSED_UNIT,
+ unwrap::PANICKING_UNWRAP,
+ unwrap::UNNECESSARY_UNWRAP,
+ unwrap_in_result::UNWRAP_IN_RESULT,
+ upper_case_acronyms::UPPER_CASE_ACRONYMS,
+ use_self::USE_SELF,
+ useless_conversion::USELESS_CONVERSION,
+ vec::USELESS_VEC,
+ vec_init_then_push::VEC_INIT_THEN_PUSH,
+ wildcard_imports::ENUM_GLOB_USE,
+ wildcard_imports::WILDCARD_IMPORTS,
+ write::PRINTLN_EMPTY_STRING,
+ write::PRINT_LITERAL,
+ write::PRINT_STDERR,
+ write::PRINT_STDOUT,
+ write::PRINT_WITH_NEWLINE,
+ write::USE_DEBUG,
+ write::WRITELN_EMPTY_STRING,
+ write::WRITE_LITERAL,
+ write::WRITE_WITH_NEWLINE,
+ zero_div_zero::ZERO_DIVIDED_BY_ZERO,
+ zero_sized_map_values::ZERO_SIZED_MAP_VALUES,
+])
--- /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.
+
+store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
+ LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
+ LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY),
+ LintId::of(copies::BRANCHES_SHARING_CODE),
++ LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
+ LintId::of(equatable_if_let::EQUATABLE_IF_LET),
+ LintId::of(fallible_impl_from::FALLIBLE_IMPL_FROM),
+ LintId::of(floating_point_arithmetic::IMPRECISE_FLOPS),
+ LintId::of(floating_point_arithmetic::SUBOPTIMAL_FLOPS),
+ LintId::of(future_not_send::FUTURE_NOT_SEND),
+ LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE),
+ LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
+ LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
+ LintId::of(methods::ITER_ON_EMPTY_COLLECTIONS),
+ LintId::of(methods::ITER_ON_SINGLE_ITEMS),
+ LintId::of(methods::ITER_WITH_DRAIN),
+ LintId::of(methods::PATH_BUF_PUSH_OVERWRITE),
+ LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
+ LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
+ LintId::of(mutex_atomic::MUTEX_ATOMIC),
+ LintId::of(mutex_atomic::MUTEX_INTEGER),
+ LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY),
+ LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
+ LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
+ LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
+ LintId::of(regex::TRIVIAL_REGEX),
+ LintId::of(strings::STRING_LIT_AS_BYTES),
+ LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
+ LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
+ LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
+ LintId::of(unused_peekable::UNUSED_PEEKABLE),
+ LintId::of(unused_rounding::UNUSED_ROUNDING),
+ LintId::of(use_self::USE_SELF),
+])
--- /dev/null
- LintId::of(doc_link_with_quotes::DOC_LINK_WITH_QUOTES),
+// 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.
+
+store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
+ LintId::of(attrs::INLINE_ALWAYS),
+ LintId::of(casts::BORROW_AS_PTR),
+ LintId::of(casts::CAST_LOSSLESS),
+ LintId::of(casts::CAST_POSSIBLE_TRUNCATION),
+ LintId::of(casts::CAST_POSSIBLE_WRAP),
+ LintId::of(casts::CAST_PRECISION_LOSS),
+ LintId::of(casts::CAST_PTR_ALIGNMENT),
+ LintId::of(casts::CAST_SIGN_LOSS),
+ LintId::of(casts::PTR_AS_PTR),
+ LintId::of(checked_conversions::CHECKED_CONVERSIONS),
+ LintId::of(copies::SAME_FUNCTIONS_IN_IF_CONDITION),
+ LintId::of(copy_iterator::COPY_ITERATOR),
+ LintId::of(default::DEFAULT_TRAIT_ACCESS),
+ LintId::of(dereference::EXPLICIT_DEREF_METHODS),
+ LintId::of(dereference::REF_BINDING_TO_REFERENCE),
+ LintId::of(derive::EXPL_IMPL_CLONE_ON_COPY),
+ LintId::of(derive::UNSAFE_DERIVE_DESERIALIZE),
++ LintId::of(doc::DOC_LINK_WITH_QUOTES),
+ LintId::of(doc::DOC_MARKDOWN),
+ LintId::of(doc::MISSING_ERRORS_DOC),
+ LintId::of(doc::MISSING_PANICS_DOC),
+ LintId::of(empty_enum::EMPTY_ENUM),
+ LintId::of(enum_variants::MODULE_NAME_REPETITIONS),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
+ LintId::of(excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
+ LintId::of(excessive_bools::STRUCT_EXCESSIVE_BOOLS),
++ LintId::of(format_args::UNINLINED_FORMAT_ARGS),
+ LintId::of(functions::MUST_USE_CANDIDATE),
+ LintId::of(functions::TOO_MANY_LINES),
+ LintId::of(if_not_else::IF_NOT_ELSE),
+ LintId::of(implicit_hasher::IMPLICIT_HASHER),
+ LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
+ LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
+ LintId::of(infinite_iter::MAYBE_INFINITE_ITER),
+ LintId::of(invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS),
+ LintId::of(items_after_statements::ITEMS_AFTER_STATEMENTS),
+ LintId::of(iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR),
+ LintId::of(large_stack_arrays::LARGE_STACK_ARRAYS),
+ LintId::of(let_underscore::LET_UNDERSCORE_DROP),
+ LintId::of(literal_representation::LARGE_DIGIT_GROUPS),
+ LintId::of(literal_representation::UNREADABLE_LITERAL),
+ LintId::of(loops::EXPLICIT_INTO_ITER_LOOP),
+ LintId::of(loops::EXPLICIT_ITER_LOOP),
+ LintId::of(macro_use::MACRO_USE_IMPORTS),
+ LintId::of(manual_assert::MANUAL_ASSERT),
+ LintId::of(manual_instant_elapsed::MANUAL_INSTANT_ELAPSED),
+ LintId::of(manual_string_new::MANUAL_STRING_NEW),
+ LintId::of(matches::MATCH_BOOL),
+ LintId::of(matches::MATCH_ON_VEC_ITEMS),
+ LintId::of(matches::MATCH_SAME_ARMS),
+ LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS),
+ LintId::of(matches::MATCH_WILD_ERR_ARM),
+ LintId::of(matches::SINGLE_MATCH_ELSE),
+ LintId::of(methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
+ LintId::of(methods::CLONED_INSTEAD_OF_COPIED),
+ LintId::of(methods::FILTER_MAP_NEXT),
+ LintId::of(methods::FLAT_MAP_OPTION),
+ LintId::of(methods::FROM_ITER_INSTEAD_OF_COLLECT),
+ LintId::of(methods::IMPLICIT_CLONE),
+ LintId::of(methods::INEFFICIENT_TO_STRING),
+ LintId::of(methods::MANUAL_OK_OR),
+ LintId::of(methods::MAP_UNWRAP_OR),
+ LintId::of(methods::NAIVE_BYTECOUNT),
+ LintId::of(methods::STABLE_SORT_PRIMITIVE),
+ LintId::of(methods::UNNECESSARY_JOIN),
+ LintId::of(misc::USED_UNDERSCORE_BINDING),
+ LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER),
+ LintId::of(mut_mut::MUT_MUT),
+ LintId::of(needless_continue::NEEDLESS_CONTINUE),
+ LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
+ LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
+ LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
+ LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
+ LintId::of(non_expressive_names::SIMILAR_NAMES),
+ LintId::of(operators::FLOAT_CMP),
+ LintId::of(operators::NEEDLESS_BITWISE_BOOL),
+ LintId::of(operators::VERBOSE_BIT_MASK),
+ LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
+ LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
+ LintId::of(ranges::RANGE_MINUS_ONE),
+ LintId::of(ranges::RANGE_PLUS_ONE),
+ LintId::of(redundant_else::REDUNDANT_ELSE),
+ LintId::of(ref_option_ref::REF_OPTION_REF),
+ LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE),
+ LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
+ LintId::of(strings::STRING_ADD_ASSIGN),
+ LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
+ LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
+ LintId::of(types::LINKEDLIST),
+ LintId::of(types::OPTION_OPTION),
+ LintId::of(unicode::UNICODE_NOT_NFC),
+ LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS),
+ LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS),
+ LintId::of(unused_async::UNUSED_ASYNC),
+ LintId::of(unused_self::UNUSED_SELF),
+ LintId::of(wildcard_imports::ENUM_GLOB_USE),
+ LintId::of(wildcard_imports::WILDCARD_IMPORTS),
+ LintId::of(zero_sized_map_values::ZERO_SIZED_MAP_VALUES),
+])
--- /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.
+
+store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
++ LintId::of(box_default::BOX_DEFAULT),
+ LintId::of(entry::MAP_ENTRY),
+ LintId::of(escape::BOXED_LOCAL),
+ LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
+ LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
+ LintId::of(functions::RESULT_LARGE_ERR),
+ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(loops::MANUAL_MEMCPY),
+ LintId::of(loops::MISSING_SPIN_LOOP),
+ LintId::of(loops::NEEDLESS_COLLECT),
+ LintId::of(manual_retain::MANUAL_RETAIN),
+ LintId::of(methods::COLLAPSIBLE_STR_REPLACE),
+ LintId::of(methods::EXPECT_FUN_CALL),
+ LintId::of(methods::EXTEND_WITH_DRAIN),
+ LintId::of(methods::ITER_NTH),
+ LintId::of(methods::ITER_OVEREAGER_CLONED),
+ LintId::of(methods::MANUAL_STR_REPEAT),
+ LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::SINGLE_CHAR_PATTERN),
+ LintId::of(methods::UNNECESSARY_TO_OWNED),
+ LintId::of(operators::CMP_OWNED),
+ LintId::of(redundant_clone::REDUNDANT_CLONE),
+ LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(types::BOX_COLLECTION),
+ LintId::of(types::REDUNDANT_ALLOCATION),
+ LintId::of(vec::USELESS_VEC),
+ LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
+])
--- /dev/null
- LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
+// 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.
+
+store.register_group(true, "clippy::style", Some("clippy_style"), vec![
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(bool_to_int_with_if::BOOL_TO_INT_WITH_IF),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
+ LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
+ LintId::of(dereference::NEEDLESS_BORROW),
++ LintId::of(disallowed_macros::DISALLOWED_MACROS),
+ LintId::of(disallowed_methods::DISALLOWED_METHODS),
+ LintId::of(disallowed_names::DISALLOWED_NAMES),
+ LintId::of(disallowed_types::DISALLOWED_TYPES),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ LintId::of(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::RESULT_UNIT_ERR),
++ LintId::of(implicit_saturating_add::IMPLICIT_SATURATING_ADD),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_bits::MANUAL_BITS),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(match_result_ok::MATCH_RESULT_OK),
+ LintId::of(matches::COLLAPSIBLE_MATCH),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MANUAL_MAP),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::ERR_EXPECT),
+ LintId::of(methods::GET_FIRST),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::IS_DIGIT_ASCII_RADIX),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MAP_CLONE),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::MUT_MUTEX_LOCK),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OBFUSCATED_IF_ELSE),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
+ LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(operators::ASSIGN_OP_PATTERN),
+ LintId::of(operators::OP_REF),
+ LintId::of(operators::PTR_EQ),
+ LintId::of(partialeq_to_none::PARTIALEQ_TO_NONE),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(strings::TRIM_SPLIT_WHITESPACE),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(unit_types::LET_UNIT_VALUE),
+ LintId::of(unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+])
--- /dev/null
- extern crate rustc_hir_analysis;
+#![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;
+extern crate rustc_hir_pretty;
+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;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
- mod doc_link_with_quotes;
+
+#[macro_use]
+extern crate clippy_utils;
+
+use clippy_utils::parse_msrv;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_lint::LintId;
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
+
+/// 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
+ }
+ };
+}
+
+#[cfg(feature = "internal")]
+pub mod deprecated_lints;
+#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
+mod utils;
+
+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;
- "error reading Clippy's configuration file. `{}` is not a valid Rust version",
- s
+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_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 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;
+mod manual_instant_elapsed;
+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 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 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 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 use crate::utils::conf::Conf;
+use crate::utils::conf::{format_error, TryConf};
+
+/// 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. `{}` is not a valid Rust version",
- s
++ "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!(
- "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{}` from `clippy.toml`",
- clippy_msrv
++ "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!(
- sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
++ "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)]
+pub fn read_conf(sess: &Session) -> Conf {
+ let file_name = match utils::conf::lookup_conf_file() {
+ Ok(Some(path)) => path,
+ Ok(None) => return Conf::default(),
+ Err(error) => {
- store.register_late_pass(|_| Box::new(utils::internal_lints::InterningDefinedSymbol::default()));
- store.register_late_pass(|_| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
- store.register_late_pass(|_| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
++ sess.struct_err(&format!("error finding Clippy's configuration file: {error}"))
+ .emit();
+ return Conf::default();
+ },
+ };
+
+ 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
+}
+
+/// 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);
+
+ include!("lib.deprecated.rs");
+
+ 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");
+
+ #[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::ClippyLintsInternal));
+ store.register_early_pass(|| Box::new(utils::internal_lints::ProduceIce));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::CollapsibleCalls));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::CompilerLintFunctions::new()));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::IfChainStyle));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::InvalidPaths));
- store.register_late_pass(|_| Box::new(shadow::Shadow::default()));
++ store.register_late_pass(|_| Box::<utils::internal_lints::InterningDefinedSymbol>::default());
++ store.register_late_pass(|_| Box::<utils::internal_lints::LintWithoutLintPass>::default());
++ store.register_late_pass(|_| Box::new(utils::internal_lints::UnnecessaryDefPath));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::OuterExpnDataPass));
+ store.register_late_pass(|_| Box::new(utils::internal_lints::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)));
+ 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::new(main_recursion::MainRecursion::default()));
++ 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::new(new_without_default::NewWithoutDefault::default()));
++ 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(useless_conversion::UselessConversion::default()));
++ 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::new(default::Default::default()));
++ 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_late_pass(|_| Box::new(mut_key::MutableKeyType));
+ 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::new(redundant_pub_crate::RedundantPubCrate::default()));
++ 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(|| Box::new(single_component_path_imports::SingleComponentPathImports));
+ let max_fn_params_bools = conf.max_fn_params_bools;
+ let max_struct_bools = conf.max_struct_bools;
+ store.register_early_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::new(macro_use::MacroUseImports::default()));
++ 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::new(vec_init_then_push::VecInitThenPush::default()));
++ 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(move |_| Box::new(format_args::FormatArgs));
++ 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_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes));
- store.register_late_pass(|_| Box::new(only_used_in_recursion::OnlyUsedInRecursion::default()));
++ 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::new(write::Write::default()));
++ 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 cargo_ignore_publish = conf.cargo_ignore_publish;
+ store.register_late_pass(move |_| {
+ Box::new(cargo::Cargo {
+ ignore_publish: cargo_ignore_publish,
+ })
+ });
- store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default()));
++ store.register_late_pass(|_| Box::<write::Write>::default());
+ 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_late_pass(|_| Box::new(std_instead_of_core::StdReexports::default()));
++ 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(|_| Box::new(manual_instant_elapsed::ManualInstantElapsed));
+ 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));
+ // 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
- ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin,
- TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate,
+use clippy_utils::diagnostics::span_lint;
+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::FnRetTy::Return;
+use rustc_hir::{
+ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem,
- checker.visit_expr(&body.value);
++ ImplItemKind, Item, ItemKind, LangItem, 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.def_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(
+ 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)",
+ );
+ }
+ 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 {
+ // 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ let mut checker = BodyLifetimeChecker {
+ lifetimes_used_in_body: false,
+ };
++ checker.visit_expr(body.value);
+ if checker.lifetimes_used_in_body {
+ return false;
+ }
+ }
+
+ // check for lifetimes from higher scopes
+ for lt in input_lts.iter().chain(output_lts.iter()) {
+ if !allowed_lts.contains(lt) {
+ return false;
+ }
+ }
+
+ // 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;
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ }
+
+ // 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
+
+ // 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)
+ } 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
+ }
+ }
+}
+
+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.
+#[must_use]
+fn unique_lifetimes(lts: &[RefLt]) -> usize {
+ lts.iter().collect::<FxHashSet<_>>().len()
+}
+
+const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce];
+
+/// A visitor usable for `rustc_front::visit::walk_ty()`.
+struct RefVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ lts: Vec<RefLt>,
+ 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(),
+ 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 CLOSURE_TRAIT_BOUNDS.iter().any(|&item| {
+ self.cx
+ .tcx
+ .lang_items()
+ .require(item)
+ .map_or(false, |id| Some(id) == trait_ref.trait_def_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),
+ }
+ }
+}
+
+/// 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
- let hex = format!("{:#X}", val);
+//! Lints concerned with the grouping of digits with underscores in integral or
+//! floating-point literal expressions.
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::numeric_literal::{NumericLiteral, Radix};
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if a long integral or floating-point constant does
+ /// not contain underscores.
+ ///
+ /// ### Why is this bad?
+ /// Reading long numbers is difficult without separators.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let _: u64 =
+ /// 61864918973511
+ /// # ;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let _: u64 =
+ /// 61_864_918_973_511
+ /// # ;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNREADABLE_LITERAL,
+ pedantic,
+ "long literal without underscores"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns for mistyped suffix in literals
+ ///
+ /// ### Why is this bad?
+ /// This is most probably a typo
+ ///
+ /// ### Known problems
+ /// - Does not match on integers too large to fit in the corresponding unsigned type
+ /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
+ ///
+ /// ### Example
+ /// ```ignore
+ /// `2_32` => `2_i32`
+ /// `250_8 => `250_u8`
+ /// ```
+ #[clippy::version = "1.30.0"]
+ pub MISTYPED_LITERAL_SUFFIXES,
+ correctness,
+ "mistyped literal suffix"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if an integral or floating-point constant is
+ /// grouped inconsistently with underscores.
+ ///
+ /// ### Why is this bad?
+ /// Readers may incorrectly interpret inconsistently
+ /// grouped digits.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let _: u64 =
+ /// 618_64_9189_73_511
+ /// # ;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let _: u64 =
+ /// 61_864_918_973_511
+ /// # ;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INCONSISTENT_DIGIT_GROUPING,
+ style,
+ "integer literals with digits grouped inconsistently"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if hexadecimal or binary literals are not grouped
+ /// by nibble or byte.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u32 = 0xFFF_FFF;
+ /// let y: u8 = 0b01_011_101;
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub UNUSUAL_BYTE_GROUPINGS,
+ style,
+ "binary or hex literals that aren't grouped by four"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if the digits of an integral or floating-point
+ /// constant are grouped into groups that
+ /// are too large.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u64 = 6186491_8973511;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LARGE_DIGIT_GROUPS,
+ pedantic,
+ "grouping digits into groups that are too large"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if there is a better representation for a numeric literal.
+ ///
+ /// ### Why is this bad?
+ /// Especially for big powers of 2 a hexadecimal representation is more
+ /// readable than a decimal representation.
+ ///
+ /// ### Example
+ /// ```text
+ /// `255` => `0xFF`
+ /// `65_535` => `0xFFFF`
+ /// `4_042_322_160` => `0xF0F0_F0F0`
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DECIMAL_LITERAL_REPRESENTATION,
+ restriction,
+ "using decimal representation when hexadecimal would be better"
+}
+
+enum WarningType {
+ UnreadableLiteral,
+ InconsistentDigitGrouping,
+ LargeDigitGroups,
+ DecimalRepresentation,
+ MistypedLiteralSuffix,
+ UnusualByteGroupings,
+}
+
+impl WarningType {
+ fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
+ match self {
+ Self::MistypedLiteralSuffix => span_lint_and_sugg(
+ cx,
+ MISTYPED_LITERAL_SUFFIXES,
+ span,
+ "mistyped literal suffix",
+ "did you mean to write",
+ suggested_format,
+ Applicability::MaybeIncorrect,
+ ),
+ Self::UnreadableLiteral => span_lint_and_sugg(
+ cx,
+ UNREADABLE_LITERAL,
+ span,
+ "long literal lacking separators",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::LargeDigitGroups => span_lint_and_sugg(
+ cx,
+ LARGE_DIGIT_GROUPS,
+ span,
+ "digit groups should be smaller",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::InconsistentDigitGrouping => span_lint_and_sugg(
+ cx,
+ INCONSISTENT_DIGIT_GROUPING,
+ span,
+ "digits grouped inconsistently by underscores",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::DecimalRepresentation => span_lint_and_sugg(
+ cx,
+ DECIMAL_LITERAL_REPRESENTATION,
+ span,
+ "integer literal has a better hexadecimal representation",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::UnusualByteGroupings => span_lint_and_sugg(
+ cx,
+ UNUSUAL_BYTE_GROUPINGS,
+ span,
+ "digits of hex or binary literal not grouped by four",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ };
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct LiteralDigitGrouping {
+ lint_fraction_readability: bool,
+}
+
+impl_lint_pass!(LiteralDigitGrouping => [
+ UNREADABLE_LITERAL,
+ INCONSISTENT_DIGIT_GROUPING,
+ LARGE_DIGIT_GROUPS,
+ MISTYPED_LITERAL_SUFFIXES,
+ UNUSUAL_BYTE_GROUPINGS,
+]);
+
+impl EarlyLintPass for LiteralDigitGrouping {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit);
+ }
+ }
+}
+
+// Length of each UUID hyphenated group in hex digits.
+const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
+
+impl LiteralDigitGrouping {
+ pub fn new(lint_fraction_readability: bool) -> Self {
+ Self {
+ lint_fraction_readability,
+ }
+ }
+
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ if_chain! {
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
+ then {
+ if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
+ return;
+ }
+
+ if Self::is_literal_uuid_formatted(&mut num_lit) {
+ return;
+ }
+
+ let result = (|| {
+
+ let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
+ if let Some(fraction) = num_lit.fraction {
+ let fractional_group_size = Self::get_group_size(
+ fraction.rsplit('_'),
+ num_lit.radix,
+ self.lint_fraction_readability)?;
+
+ let consistent = Self::parts_consistent(integral_group_size,
+ fractional_group_size,
+ num_lit.integer.len(),
+ fraction.len());
+ if !consistent {
+ return Err(WarningType::InconsistentDigitGrouping);
+ };
+ }
+
+ Ok(())
+ })();
+
+
+ if let Err(warning_type) = result {
+ let should_warn = match warning_type {
+ | WarningType::UnreadableLiteral
+ | WarningType::InconsistentDigitGrouping
+ | WarningType::UnusualByteGroupings
+ | WarningType::LargeDigitGroups => {
+ !lit.span.from_expansion()
+ }
+ WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
+ true
+ }
+ };
+ if should_warn {
+ warning_type.display(num_lit.format(), cx, lit.span);
+ }
+ }
+ }
+ }
+ }
+
+ // Returns `false` if the check fails
+ fn check_for_mistyped_suffix(
+ cx: &EarlyContext<'_>,
+ span: rustc_span::Span,
+ num_lit: &mut NumericLiteral<'_>,
+ ) -> bool {
+ if num_lit.suffix.is_some() {
+ return true;
+ }
+
+ let (part, mistyped_suffixes, is_float) = if let Some((_, exponent)) = &mut num_lit.exponent {
+ (exponent, &["32", "64"][..], true)
+ } else if num_lit.fraction.is_some() {
+ return true;
+ } else {
+ (&mut num_lit.integer, &["8", "16", "32", "64"][..], false)
+ };
+
+ let mut split = part.rsplit('_');
+ let last_group = split.next().expect("At least one group");
+ if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
+ let main_part = &part[..part.len() - last_group.len()];
+ let missing_char;
+ if is_float {
+ missing_char = 'f';
+ } else {
+ let radix = match num_lit.radix {
+ Radix::Binary => 2,
+ Radix::Octal => 8,
+ Radix::Decimal => 10,
+ Radix::Hexadecimal => 16,
+ };
+ if let Ok(int) = u64::from_str_radix(&main_part.replace('_', ""), radix) {
+ missing_char = match (last_group, int) {
+ ("8", i) if i8::try_from(i).is_ok() => 'i',
+ ("16", i) if i16::try_from(i).is_ok() => 'i',
+ ("32", i) if i32::try_from(i).is_ok() => 'i',
+ ("64", i) if i64::try_from(i).is_ok() => 'i',
+ ("8", u) if u8::try_from(u).is_ok() => 'u',
+ ("16", u) if u16::try_from(u).is_ok() => 'u',
+ ("32", u) if u32::try_from(u).is_ok() => 'u',
+ ("64", _) => 'u',
+ _ => {
+ return true;
+ },
+ }
+ } else {
+ return true;
+ }
+ }
+ *part = main_part;
+ let mut sugg = num_lit.format();
+ sugg.push('_');
+ sugg.push(missing_char);
+ sugg.push_str(last_group);
+ WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
+ false
+ } else {
+ true
+ }
+ }
+
+ /// Checks whether the numeric literal matches the formatting of a UUID.
+ ///
+ /// Returns `true` if the radix is hexadecimal, and the groups match the
+ /// UUID format of 8-4-4-4-12.
+ fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool {
+ if num_lit.radix != Radix::Hexadecimal {
+ return false;
+ }
+
+ // UUIDs should not have a fraction
+ if num_lit.fraction.is_some() {
+ return false;
+ }
+
+ let group_sizes: Vec<usize> = num_lit.integer.split('_').map(str::len).collect();
+ if UUID_GROUP_LENS.len() == group_sizes.len() {
+ iter::zip(&UUID_GROUP_LENS, &group_sizes).all(|(&a, &b)| a == b)
+ } else {
+ false
+ }
+ }
+
+ /// Given the sizes of the digit groups of both integral and fractional
+ /// parts, and the length
+ /// of both parts, determine if the digits have been grouped consistently.
+ #[must_use]
+ fn parts_consistent(
+ int_group_size: Option<usize>,
+ frac_group_size: Option<usize>,
+ int_size: usize,
+ frac_size: usize,
+ ) -> bool {
+ match (int_group_size, frac_group_size) {
+ // No groups on either side of decimal point - trivially consistent.
+ (None, None) => true,
+ // Integral part has grouped digits, fractional part does not.
+ (Some(int_group_size), None) => frac_size <= int_group_size,
+ // Fractional part has grouped digits, integral part does not.
+ (None, Some(frac_group_size)) => int_size <= frac_group_size,
+ // Both parts have grouped digits. Groups should be the same size.
+ (Some(int_group_size), Some(frac_group_size)) => int_group_size == frac_group_size,
+ }
+ }
+
+ /// Returns the size of the digit groups (or None if ungrouped) if successful,
+ /// otherwise returns a `WarningType` for linting.
+ fn get_group_size<'a>(
+ groups: impl Iterator<Item = &'a str>,
+ radix: Radix,
+ lint_unreadable: bool,
+ ) -> Result<Option<usize>, WarningType> {
+ let mut groups = groups.map(str::len);
+
+ let first = groups.next().expect("At least one group");
+
+ if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) {
+ return Err(WarningType::UnusualByteGroupings);
+ }
+
+ if let Some(second) = groups.next() {
+ if !groups.all(|x| x == second) || first > second {
+ Err(WarningType::InconsistentDigitGrouping)
+ } else if second > 4 {
+ Err(WarningType::LargeDigitGroups)
+ } else {
+ Ok(Some(second))
+ }
+ } else if first > 5 && lint_unreadable {
+ Err(WarningType::UnreadableLiteral)
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+#[expect(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct DecimalLiteralRepresentation {
+ threshold: u64,
+}
+
+impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
+
+impl EarlyLintPass for DecimalLiteralRepresentation {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit);
+ }
+ }
+}
+
+impl DecimalLiteralRepresentation {
+ #[must_use]
+ pub fn new(threshold: u64) -> Self {
+ Self { threshold }
+ }
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ // Lint integral literals.
+ if_chain! {
+ if let LitKind::Int(val, _) = lit.kind;
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(num_lit) = NumericLiteral::from_lit(&src, lit);
+ if num_lit.radix == Radix::Decimal;
+ if val >= u128::from(self.threshold);
+ then {
++ let hex = format!("{val:#X}");
+ let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
+ let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
+ warning_type.display(num_lit.format(), cx, lit.span);
+ });
+ }
+ }
+ }
+
+ fn do_lint(digits: &str) -> Result<(), WarningType> {
+ if digits.len() == 1 {
+ // Lint for 1 digit literals, if someone really sets the threshold that low
+ if digits == "1"
+ || digits == "2"
+ || digits == "4"
+ || digits == "8"
+ || digits == "3"
+ || digits == "7"
+ || digits == "F"
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else if digits.len() < 4 {
+ // Lint for Literals with a hex-representation of 2 or 3 digits
+ let f = &digits[0..1]; // first digit
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
+ // Powers of 2 minus 1
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else {
+ // Lint for Literals with a hex-representation of 4 digits or more
+ let f = &digits[0..1]; // first digit
+ let m = &digits[1..digits.len() - 1]; // middle digits, except last
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2 with a margin of +15/-16
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
+ // Lint for representations with only 0s and Fs, while allowing 7 as the first
+ // digit
+ || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ }
+
+ Ok(())
+ }
+}
--- /dev/null
- &format!("the variable `{}` is used as a loop counter", name),
+use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{get_enclosing_block, is_integer_const};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr};
+use rustc_hir::{Expr, Pat};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty, UintTy};
+
+// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be
+// incremented exactly once in the loop body, and initialized to zero
+// at the start of the loop.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) {
+ // Look for variables that are incremented once per loop iteration.
+ let mut increment_visitor = IncrementVisitor::new(cx);
+ walk_expr(&mut increment_visitor, body);
+
+ // For each candidate, check the parent block to see if
+ // it's initialized to zero at the start of the loop.
+ if let Some(block) = get_enclosing_block(cx, expr.hir_id) {
+ for id in increment_visitor.into_results() {
+ let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
+ walk_block(&mut initialize_visitor, block);
+
+ if_chain! {
+ if let Some((name, ty, initializer)) = initialize_visitor.get_result();
+ if is_integer_const(cx, initializer, 0);
+ then {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let span = expr.span.with_hi(arg.span.hi());
+
+ let int_name = match ty.map(Ty::kind) {
+ // usize or inferred
+ Some(ty::Uint(UintTy::Usize)) | None => {
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_COUNTER_LOOP,
+ span,
- "for ({}, {}) in {}.enumerate()",
- name,
++ &format!("the variable `{name}` is used as a loop counter"),
+ "consider using",
+ format!(
- &format!("the variable `{}` is used as a loop counter", name),
++ "for ({name}, {}) in {}.enumerate()",
+ snippet_with_applicability(cx, pat.span, "item", &mut applicability),
+ make_iterator_snippet(cx, arg, &mut applicability),
+ ),
+ applicability,
+ );
+ return;
+ }
+ Some(ty::Int(int_ty)) => int_ty.name_str(),
+ Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
+ _ => return,
+ };
+
+ span_lint_and_then(
+ cx,
+ EXPLICIT_COUNTER_LOOP,
+ span,
- "for ({}, {}) in (0_{}..).zip({})",
- name,
++ &format!("the variable `{name}` is used as a loop counter"),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "consider using",
+ format!(
- int_name,
++ "for ({name}, {}) in (0_{int_name}..).zip({})",
+ snippet_with_applicability(cx, pat.span, "item", &mut applicability),
- "`{}` is of type `{}`, making it ineligible for `Iterator::enumerate`",
- name, int_name
+ make_iterator_snippet(cx, arg, &mut applicability),
+ ),
+ applicability,
+ );
+
+ diag.note(&format!(
++ "`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
+ ));
+ },
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- format!("&{}{}", muta, object),
+use super::EXPLICIT_ITER_LOOP;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, Mutability};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::sym;
+
+pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) {
+ let should_lint = match method_name {
+ "iter" | "iter_mut" => is_ref_iterable_type(cx, self_arg),
+ "into_iter" if is_trait_method(cx, arg, sym::IntoIterator) => {
+ let receiver_ty = cx.typeck_results().expr_ty(self_arg);
+ let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
+ let ref_receiver_ty = cx.tcx.mk_ref(
+ cx.tcx.lifetimes.re_erased,
+ ty::TypeAndMut {
+ ty: receiver_ty,
+ mutbl: Mutability::Not,
+ },
+ );
+ receiver_ty_adjusted == ref_receiver_ty
+ },
+ _ => false,
+ };
+
+ if !should_lint {
+ return;
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
+ let muta = if method_name == "iter_mut" { "mut " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_ITER_LOOP,
+ arg.span,
+ "it is more concise to loop over references to containers instead of using explicit \
+ iteration methods",
+ "to write this more concisely, try",
++ format!("&{muta}{object}"),
+ applicability,
+ );
+}
+
+/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
+/// for `&T` and `&mut T`, such as `Vec`.
+#[rustfmt::skip]
+fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ // no walk_ptrs_ty: calling iter() on a reference can make sense because it
+ // will allow further borrows afterwards
+ let ty = cx.typeck_results().expr_ty(e);
+ is_iterable_array(ty, cx) ||
+ is_type_diagnostic_item(cx, ty, sym::Vec) ||
+ is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
+ is_type_diagnostic_item(cx, ty, sym::HashMap) ||
+ is_type_diagnostic_item(cx, ty, sym::HashSet) ||
+ is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
+ is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
+ is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
+ is_type_diagnostic_item(cx, ty, sym::BTreeSet)
+}
+
+fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ // IntoIterator is currently only implemented for array sizes <= 32 in rustc
+ match ty.kind() {
+ ty::Array(_, n) => n
+ .try_eval_usize(cx.tcx, cx.param_env)
+ .map_or(false, |val| (0..=32).contains(&val)),
+ _ => false,
+ }
+}
--- /dev/null
- &format!("you seem to want to iterate on a map's {}s", kind),
+use super::FOR_KV_MAP;
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::is_local_used;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+/// Checks for the `FOR_KV_MAP` lint.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
+ let pat_span = pat.span;
+
+ if let PatKind::Tuple(pat, _) = pat.kind {
+ if pat.len() == 2 {
+ let arg_span = arg.span;
+ let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
+ ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
+ (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl),
+ (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not),
+ _ => return,
+ },
+ _ => return,
+ };
+ let mutbl = match mutbl {
+ Mutability::Not => "",
+ Mutability::Mut => "_mut",
+ };
+ let arg = match arg.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr,
+ _ => arg,
+ };
+
+ if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
+ span_lint_and_then(
+ cx,
+ FOR_KV_MAP,
+ arg_span,
- (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)),
++ &format!("you seem to want to iterate on a map's {kind}s"),
+ |diag| {
+ let map = sugg::Sugg::hir(cx, arg, "map");
+ multispan_sugg(
+ diag,
+ "use the corresponding method",
+ vec![
+ (pat_span, snippet(cx, new_pat_span, kind).into_owned()),
++ (arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_par())),
+ ],
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`.
+fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
+ match *pat {
+ PatKind::Wild => true,
+ PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
+ _ => false,
+ }
+}
--- /dev/null
- diagnostics::span_lint_and_then, higher, is_lang_ctor, path_res, peel_blocks_with_stmt,
+use super::utils::make_iterator_snippet;
+use super::MANUAL_FIND;
+use clippy_utils::{
- if let ExprKind::Call(Expr { kind: ExprKind::Path(ctor), .. }, [inner_ret]) = ret_value.kind;
- if is_lang_ctor(cx, ctor, LangItem::OptionSome);
++ diagnostics::span_lint_and_then, higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt,
+ source::snippet_with_applicability, ty::implements_trait,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ def::Res, lang_items::LangItem, BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind,
+};
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ span: Span,
+ expr: &'tcx Expr<'_>,
+) {
+ let inner_expr = peel_blocks_with_stmt(body);
+ // Check for the specific case that the result is returned and optimize suggestion for that (more
+ // cases can be added later)
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr);
+ if let Some(binding_id) = get_binding(pat);
+ if let ExprKind::Block(block, _) = then.kind;
+ if let [stmt] = block.stmts;
+ if let StmtKind::Semi(semi) = stmt.kind;
+ if let ExprKind::Ret(Some(ret_value)) = semi.kind;
- if let ExprKind::Path(path) = &last_ret.kind;
- if is_lang_ctor(cx, path, LangItem::OptionNone);
++ if let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind;
++ if is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome);
+ if path_res(cx, inner_ret) == Res::Local(binding_id);
+ if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
+ // Checks if `pat` is a single reference to a binding (`&x`)
+ let is_ref_to_binding =
+ matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
+ // If `pat` is not a binding or a reference to a binding (`x` or `&x`)
+ // we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
+ if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
+ snippet.push_str(
+ &format!(
+ ".map(|{}| {})",
+ snippet_with_applicability(cx, pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
+ )[..],
+ );
+ }
+ let ty = cx.typeck_results().expr_ty(inner_ret);
+ if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) {
+ snippet.push_str(
+ &format!(
+ ".find(|{}{}| {})",
+ "&".repeat(1 + usize::from(is_ref_to_binding)),
+ snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
+ snippet_with_applicability(cx, cond.span, "..", &mut applicability),
+ )[..],
+ );
+ if is_ref_to_binding {
+ snippet.push_str(".copied()");
+ }
+ } else {
+ applicability = Applicability::MaybeIncorrect;
+ snippet.push_str(
+ &format!(
+ ".find(|{}| {})",
+ snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
+ snippet_with_applicability(cx, cond.span, "..", &mut applicability),
+ )[..],
+ );
+ }
+ // Extends to `last_stmt` to include semicolon in case of `return None;`
+ let lint_span = span.to(last_stmt.span).to(last_ret.span);
+ span_lint_and_then(
+ cx,
+ MANUAL_FIND,
+ lint_span,
+ "manual implementation of `Iterator::find`",
+ |diag| {
+ if applicability == Applicability::MaybeIncorrect {
+ diag.note("you may need to dereference some variables");
+ }
+ diag.span_suggestion(
+ lint_span,
+ "replace with an iterator",
+ snippet,
+ applicability,
+ );
+ },
+ );
+ }
+ }
+}
+
+fn get_binding(pat: &Pat<'_>) -> Option<HirId> {
+ let mut hir_id = None;
+ let mut count = 0;
+ pat.each_binding(|annotation, id, _, _| {
+ count += 1;
+ if count > 1 {
+ hir_id = None;
+ return;
+ }
+ if let BindingAnnotation::NONE = annotation {
+ hir_id = Some(id);
+ }
+ });
+ hir_id
+}
+
+// Returns the last statement and last return if function fits format for lint
+fn last_stmt_and_ret<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> {
+ // Returns last non-return statement and the last return
+ fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> {
+ if let [.., last_stmt] = block.stmts {
+ if let Some(ret) = block.expr {
+ return Some((last_stmt, ret));
+ }
+ if_chain! {
+ if let [.., snd_last, _] = block.stmts;
+ if let StmtKind::Semi(last_expr) = last_stmt.kind;
+ if let ExprKind::Ret(Some(ret)) = last_expr.kind;
+ then {
+ return Some((snd_last, ret));
+ }
+ }
+ }
+ None
+ }
+ let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id);
+ if_chain! {
+ // This should be the loop
+ if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
+ // This should be the function body
+ if let Some((_, Node::Block(block))) = parent_iter.next();
+ if let Some((last_stmt, last_ret)) = extract(block);
+ if last_stmt.hir_id == node_hir;
++ if is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone);
+ if let Some((_, Node::Expr(_block))) = parent_iter.next();
+ // This includes the function header
+ if let Some((_, func)) = parent_iter.next();
+ if func.fn_kind().is_some();
+ then {
+ Some((block.stmts.last().unwrap(), last_ret))
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{is_lang_ctor, path_to_local_id, peel_blocks_with_stmt};
+use super::utils::make_iterator_snippet;
+use super::MANUAL_FLATTEN;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher;
+use clippy_utils::visitors::is_local_used;
- use rustc_hir::LangItem::{OptionSome, ResultOk};
++use clippy_utils::{path_to_local_id, peel_blocks_with_stmt};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- use rustc_middle::ty;
++use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Expr, Pat, PatKind};
+use rustc_lint::LateContext;
- let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
- let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
++use rustc_middle::ty::{self, DefIdTree};
+use rustc_span::source_map::Span;
+
+/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
+/// iterator element is used.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ span: Span,
+) {
+ let inner_expr = peel_blocks_with_stmt(body);
+ if_chain! {
+ if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
+ = higher::IfLet::hir(cx, inner_expr);
+ // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
+ if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
+ if path_to_local_id(let_expr, pat_hir_id);
+ // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
+ if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
- let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);
++ if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id);
++ if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
++ let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id);
++ let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id);
+ if some_ctor || ok_ctor;
+ // Ensure expr in `if let` is not used afterwards
+ if !is_local_used(cx, if_then, pat_hir_id);
+ then {
+ let if_let_type = if some_ctor { "Some" } else { "Ok" };
+ // Prepare the error message
++ let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
+
+ // Prepare the help message
+ let mut applicability = Applicability::MaybeIncorrect;
+ let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
+ let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
+ ty::Ref(_, inner, _) => match inner.kind() {
+ ty::Ref(..) => ".copied()",
+ _ => ""
+ }
+ _ => ""
+ };
+
+ let sugg = format!("{arg_snippet}{copied}.flatten()");
+
+ // If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
+ // it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
+ // to refer to the correct relative position of the suggestion.
+ let help_msg = if sugg.contains('\n') {
+ "remove the `if let` statement in the for loop and then..."
+ } else {
+ "...and remove the `if let` statement in the for loop"
+ };
+
+ span_lint_and_then(
+ cx,
+ MANUAL_FLATTEN,
+ span,
+ &msg,
+ |diag| {
+ diag.span_suggestion(
+ arg.span,
+ "try",
+ sugg,
+ applicability,
+ );
+ diag.span_help(
+ inner_expr.span,
+ help_msg,
+ );
+ }
+ );
+ }
+ }
+}
--- /dev/null
- format!(
- "{}[{}..{}]",
- dst_base_str,
- dst_offset.maybe_par(),
- dst_limit.maybe_par()
- )
- .into()
+use super::{IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::walk_block;
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+use std::fmt::Display;
+use std::iter::Iterator;
+
+/// Checks for for loops that sequentially copy items from one slice-like
+/// object to another.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) -> bool {
+ if let Some(higher::Range {
+ start: Some(start),
+ end: Some(end),
+ limits,
+ }) = higher::Range::hir(arg)
+ {
+ // the var must be a single name
+ if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
+ let mut starts = vec![Start {
+ id: canonical_id,
+ kind: StartKind::Range,
+ }];
+
+ // This is one of few ways to return different iterators
+ // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
+ let mut iter_a = None;
+ let mut iter_b = None;
+
+ if let ExprKind::Block(block, _) = body.kind {
+ if let Some(loop_counters) = get_loop_counters(cx, block, expr) {
+ starts.extend(loop_counters);
+ }
+ iter_a = Some(get_assignments(block, &starts));
+ } else {
+ iter_b = Some(get_assignment(body));
+ }
+
+ let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter());
+
+ let big_sugg = assignments
+ // The only statements in the for loops can be indexed assignments from
+ // indexed retrievals (except increments of loop counters).
+ .map(|o| {
+ o.and_then(|(lhs, rhs)| {
+ let rhs = fetch_cloned_expr(rhs);
+ if_chain! {
+ if let ExprKind::Index(base_left, idx_left) = lhs.kind;
+ if let ExprKind::Index(base_right, idx_right) = rhs.kind;
+ if let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left));
+ if get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some();
+ if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts);
+ if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
+
+ // Source and destination must be different
+ if path_to_local(base_left) != path_to_local(base_right);
+ then {
+ Some((ty, IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left },
+ IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }))
+ } else {
+ None
+ }
+ }
+ })
+ })
+ .map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src)))
+ .collect::<Option<Vec<_>>>()
+ .filter(|v| !v.is_empty())
+ .map(|v| v.join("\n "));
+
+ if let Some(big_sugg) = big_sugg {
+ span_lint_and_sugg(
+ cx,
+ MANUAL_MEMCPY,
+ expr.span,
+ "it looks like you're manually copying between slices",
+ "try replacing the loop by",
+ big_sugg,
+ Applicability::Unspecified,
+ );
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn build_manual_memcpy_suggestion<'tcx>(
+ cx: &LateContext<'tcx>,
+ start: &Expr<'_>,
+ end: &Expr<'_>,
+ limits: ast::RangeLimits,
+ elem_ty: Ty<'tcx>,
+ dst: &IndexExpr<'_>,
+ src: &IndexExpr<'_>,
+) -> String {
+ fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ if offset.to_string() == "0" {
+ sugg::EMPTY.into()
+ } else {
+ offset
+ }
+ }
+
+ let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
+ if_chain! {
+ if let ExprKind::MethodCall(method, recv, [], _) = end.kind;
+ if method.ident.name == sym::len;
+ if path_to_local(recv) == path_to_local(base);
+ then {
+ if sugg.to_string() == end_str {
+ sugg::EMPTY.into()
+ } else {
+ sugg
+ }
+ } else {
+ match limits {
+ ast::RangeLimits::Closed => {
+ sugg + &sugg::ONE.into()
+ },
+ ast::RangeLimits::HalfOpen => sugg,
+ }
+ }
+ }
+ };
+
+ let start_str = Sugg::hir(cx, start, "").into();
+ let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into();
+
+ let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx {
+ StartKind::Range => (
+ print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(),
+ print_limit(
+ end,
+ end_str.to_string().as_str(),
+ idx_expr.base,
+ apply_offset(&end_str, &idx_expr.idx_offset),
+ )
+ .into_sugg(),
+ ),
+ StartKind::Counter { initializer } => {
+ let counter_start = Sugg::hir(cx, initializer, "").into();
+ (
+ print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(),
+ print_limit(
+ end,
+ end_str.to_string().as_str(),
+ idx_expr.base,
+ apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str,
+ )
+ .into_sugg(),
+ )
+ },
+ };
+
+ let (dst_offset, dst_limit) = print_offset_and_limit(dst);
+ let (src_offset, src_limit) = print_offset_and_limit(src);
+
+ let dst_base_str = snippet(cx, dst.base.span, "???");
+ let src_base_str = snippet(cx, src.base.span, "???");
+
+ let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY {
+ dst_base_str
+ } else {
- "{}.{}(&{}[{}..{}]);",
- dst,
- method_str,
- src_base_str,
++ format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into()
+ };
+
+ let method_str = if is_copy(cx, elem_ty) {
+ "copy_from_slice"
+ } else {
+ "clone_from_slice"
+ };
+
+ format!(
++ "{dst}.{method_str}(&{src_base_str}[{}..{}]);",
+ src_offset.maybe_par(),
+ src_limit.maybe_par()
+ )
+}
+
+/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`;
+/// and also, it avoids subtracting a variable from the same one by replacing it with `0`.
+/// it exists for the convenience of the overloaded operators while normal functions can do the
+/// same.
+#[derive(Clone)]
+struct MinifyingSugg<'a>(Sugg<'a>);
+
+impl<'a> Display for MinifyingSugg<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<'a> MinifyingSugg<'a> {
+ fn into_sugg(self) -> Sugg<'a> {
+ self.0
+ }
+}
+
+impl<'a> From<Sugg<'a>> for MinifyingSugg<'a> {
+ fn from(sugg: Sugg<'a>) -> Self {
+ Self(sugg)
+ }
+}
+
+impl std::ops::Add for &MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.to_string().as_str(), rhs.to_string().as_str()) {
+ ("0", _) => rhs.clone(),
+ (_, "0") => self.clone(),
+ (_, _) => (&self.0 + &rhs.0).into(),
+ }
+ }
+}
+
+impl std::ops::Sub for &MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.to_string().as_str(), rhs.to_string().as_str()) {
+ (_, "0") => self.clone(),
+ ("0", _) => (-rhs.0.clone()).into(),
+ (x, y) if x == y => sugg::ZERO.into(),
+ (_, _) => (&self.0 - &rhs.0).into(),
+ }
+ }
+}
+
+impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.to_string().as_str(), rhs.to_string().as_str()) {
+ ("0", _) => rhs.clone(),
+ (_, "0") => self,
+ (_, _) => (self.0 + &rhs.0).into(),
+ }
+ }
+}
+
+impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.to_string().as_str(), rhs.to_string().as_str()) {
+ (_, "0") => self,
+ ("0", _) => (-rhs.0.clone()).into(),
+ (x, y) if x == y => sugg::ZERO.into(),
+ (_, _) => (self.0 - &rhs.0).into(),
+ }
+ }
+}
+
+/// a wrapper around `MinifyingSugg`, which carries an operator like currying
+/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`).
+struct Offset {
+ value: MinifyingSugg<'static>,
+ sign: OffsetSign,
+}
+
+#[derive(Clone, Copy)]
+enum OffsetSign {
+ Positive,
+ Negative,
+}
+
+impl Offset {
+ fn negative(value: Sugg<'static>) -> Self {
+ Self {
+ value: value.into(),
+ sign: OffsetSign::Negative,
+ }
+ }
+
+ fn positive(value: Sugg<'static>) -> Self {
+ Self {
+ value: value.into(),
+ sign: OffsetSign::Positive,
+ }
+ }
+
+ fn empty() -> Self {
+ Self::positive(sugg::ZERO)
+ }
+}
+
+fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> {
+ match rhs.sign {
+ OffsetSign::Positive => lhs + &rhs.value,
+ OffsetSign::Negative => lhs - &rhs.value,
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum StartKind<'hir> {
+ Range,
+ Counter { initializer: &'hir Expr<'hir> },
+}
+
+struct IndexExpr<'hir> {
+ base: &'hir Expr<'hir>,
+ idx: StartKind<'hir>,
+ idx_offset: Offset,
+}
+
+struct Start<'hir> {
+ id: HirId,
+ kind: StartKind<'hir>,
+}
+
+fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ match ty.kind() {
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did()) => Some(subs.type_at(0)),
+ ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, *subty),
+ ty::Slice(ty) | ty::Array(ty, _) => Some(*ty),
+ _ => None,
+ }
+}
+
+fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+ if_chain! {
+ if let ExprKind::MethodCall(method, arg, [], _) = expr.kind;
+ if method.ident.name == sym::clone;
+ then { arg } else { expr }
+ }
+}
+
+fn get_details_from_idx<'tcx>(
+ cx: &LateContext<'tcx>,
+ idx: &Expr<'_>,
+ starts: &[Start<'tcx>],
+) -> Option<(StartKind<'tcx>, Offset)> {
+ fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<StartKind<'tcx>> {
+ let id = path_to_local(e)?;
+ starts.iter().find(|start| start.id == id).map(|start| start.kind)
+ }
+
+ fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<Sugg<'static>> {
+ match &e.kind {
+ ExprKind::Lit(l) => match l.node {
+ ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())),
+ _ => None,
+ },
+ ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")),
+ _ => None,
+ }
+ }
+
+ match idx.kind {
+ ExprKind::Binary(op, lhs, rhs) => match op.node {
+ BinOpKind::Add => {
+ let offset_opt = get_start(lhs, starts)
+ .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o)))
+ .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o))));
+
+ offset_opt.map(|(s, o)| (s, Offset::positive(o)))
+ },
+ BinOpKind::Sub => {
+ get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o))))
+ },
+ _ => None,
+ },
+ ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())),
+ _ => None,
+ }
+}
+
+fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+ if let ExprKind::Assign(lhs, rhs, _) = e.kind {
+ Some((lhs, rhs))
+ } else {
+ None
+ }
+}
+
+/// Get assignments from the given block.
+/// The returned iterator yields `None` if no assignment expressions are there,
+/// filtering out the increments of the given whitelisted loop counters;
+/// because its job is to make sure there's nothing other than assignments and the increments.
+fn get_assignments<'a, 'tcx>(
+ Block { stmts, expr, .. }: &'tcx Block<'tcx>,
+ loop_counters: &'a [Start<'tcx>],
+) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'a {
+ // As the `filter` and `map` below do different things, I think putting together
+ // just increases complexity. (cc #3188 and #4193)
+ stmts
+ .iter()
+ .filter_map(move |stmt| match stmt.kind {
+ StmtKind::Local(..) | StmtKind::Item(..) => None,
+ StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e),
+ })
+ .chain((*expr).into_iter())
+ .filter(move |e| {
+ if let ExprKind::AssignOp(_, place, _) = e.kind {
+ path_to_local(place).map_or(false, |id| {
+ !loop_counters
+ .iter()
+ // skip the first item which should be `StartKind::Range`
+ // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop.
+ .skip(1)
+ .any(|counter| counter.id == id)
+ })
+ } else {
+ true
+ }
+ })
+ .map(get_assignment)
+}
+
+fn get_loop_counters<'a, 'tcx>(
+ cx: &'a LateContext<'tcx>,
+ body: &'tcx Block<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<impl Iterator<Item = Start<'tcx>> + 'a> {
+ // Look for variables that are incremented once per loop iteration.
+ let mut increment_visitor = IncrementVisitor::new(cx);
+ walk_block(&mut increment_visitor, body);
+
+ // For each candidate, check the parent block to see if
+ // it's initialized to zero at the start of the loop.
+ get_enclosing_block(cx, expr.hir_id).and_then(|block| {
+ increment_visitor
+ .into_results()
+ .filter_map(move |var_id| {
+ let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id);
+ walk_block(&mut initialize_visitor, block);
+
+ initialize_visitor.get_result().map(|(_, _, initializer)| Start {
+ id: var_id,
+ kind: StartKind::Counter { initializer },
+ })
+ })
+ .into()
+ })
+}
--- /dev/null
- #[clippy::version = "1.61.0"]
+mod empty_loop;
+mod explicit_counter_loop;
+mod explicit_into_iter_loop;
+mod explicit_iter_loop;
+mod for_kv_map;
+mod for_loops_over_fallibles;
+mod iter_next_loop;
+mod manual_find;
+mod manual_flatten;
+mod manual_memcpy;
+mod missing_spin_loop;
+mod mut_range_bound;
+mod needless_collect;
+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
+ /// Checks for `for` loops over `Option` or `Result` values.
+ ///
+ /// ### Why is this bad?
+ /// Readability. This is more clearly expressed as an `if
+ /// let`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let opt = Some(1);
+ /// # let res: Result<i32, std::io::Error> = Ok(1);
+ /// for x in opt {
+ /// // ..
+ /// }
+ ///
+ /// for x in &res {
+ /// // ..
+ /// }
+ ///
+ /// for x in res.iter() {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let opt = Some(1);
+ /// # let res: Result<i32, std::io::Error> = Ok(1);
+ /// if let Some(x) = opt {
+ /// // ..
+ /// }
+ ///
+ /// if let Ok(x) = res {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub FOR_LOOPS_OVER_FALLIBLES,
+ suspicious,
+ "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
+}
+
+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"
+}
+
+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"
+}
+
+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,
+ FOR_LOOPS_OVER_FALLIBLES,
+ WHILE_LET_LOOP,
+ NEEDLESS_COLLECT,
+ 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);
+ }
+
+ needless_collect::check(expr, cx);
+ }
+}
+
+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: &Pat<'_>, arg: &Expr<'_>) {
+ let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
+
+ 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);
+ for_loops_over_fallibles::check(cx, pat, self_arg, Some(method_name));
+ },
+ "into_iter" => {
+ explicit_iter_loop::check(cx, self_arg, arg, method_name);
+ explicit_into_iter_loop::check(cx, self_arg, arg);
+ for_loops_over_fallibles::check(cx, pat, self_arg, Some(method_name));
+ },
+ "next" => {
+ next_loop_linted = iter_next_loop::check(cx, arg);
+ },
+ _ => {},
+ }
+ }
+
+ if !next_loop_linted {
+ for_loops_over_fallibles::check(cx, pat, arg, None);
+ }
+}
--- /dev/null
- use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+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_analysis::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;
- fn fake_read(&mut self, _: &rustc_hir_analysis::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+
+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 check_for_mutation<'tcx>(
+ cx: &LateContext<'tcx>,
+ 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,
+ };
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ 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_analysis::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
- format!("any(|{}| x == {})", arg, pred)
+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;
+use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::{Applicability, MultiSpan};
+use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
+use rustc_hir::{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::subst::GenericArgKind;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::sym;
+use rustc_span::Span;
+
+const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
+
+pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ check_needless_collect_direct_usage(expr, cx);
+ check_needless_collect_indirect_usage(expr, cx);
+}
+fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ if_chain! {
+ if let ExprKind::MethodCall(method, receiver, args, _) = expr.kind;
+ if let ExprKind::MethodCall(chain_method, ..) = receiver.kind;
+ if chain_method.ident.name == sym!(collect) && is_trait_method(cx, receiver, sym::Iterator);
+ then {
+ let ty = cx.typeck_results().expr_ty(receiver);
+ let mut applicability = Applicability::MaybeIncorrect;
+ let is_empty_sugg = "next().is_none()".to_string();
+ let method_name = method.ident.name.as_str();
+ let sugg = if is_type_diagnostic_item(cx, ty, sym::Vec) ||
+ is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
+ is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
+ is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
+ match method_name {
+ "len" => "count()".to_string(),
+ "is_empty" => is_empty_sugg,
+ "contains" => {
+ let contains_arg = snippet_with_applicability(cx, args[0].span, "??", &mut applicability);
+ let (arg, pred) = contains_arg
+ .strip_prefix('&')
+ .map_or(("&x", &*contains_arg), |s| ("x", s));
- format!(".any(|x| x == {})", stripped)
++ format!("any(|{arg}| x == {pred})")
+ }
+ _ => return,
+ }
+ }
+ else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
+ is_type_diagnostic_item(cx, ty, sym::HashMap) {
+ match method_name {
+ "is_empty" => is_empty_sugg,
+ _ => return,
+ }
+ }
+ else {
+ return;
+ };
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_COLLECT,
+ chain_method.ident.span.with_hi(expr.span.hi()),
+ NEEDLESS_COLLECT_MSG,
+ "replace with",
+ sugg,
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ if let ExprKind::Block(block, _) = expr.kind {
+ for stmt in block.stmts {
+ if_chain! {
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(_, id, ..) = local.pat.kind;
+ if let Some(init_expr) = local.init;
+ if let ExprKind::MethodCall(method_name, iter_source, [], ..) = init_expr.kind;
+ if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
+ let ty = cx.typeck_results().expr_ty(init_expr);
+ if is_type_diagnostic_item(cx, ty, sym::Vec) ||
+ is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
+ is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
+ is_type_diagnostic_item(cx, ty, sym::LinkedList);
+ let iter_ty = cx.typeck_results().expr_ty(iter_source);
+ if let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty));
+ if let [iter_call] = &*iter_calls;
+ then {
+ 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(method_name.ident.span);
+ span.push_span_label(iter_call.span, "the iterator could be used here instead");
+ span_lint_hir_and_then(
+ cx,
+ super::NEEDLESS_COLLECT,
+ init_expr.hir_id,
+ span,
+ NEEDLESS_COLLECT_MSG,
+ |diag| {
+ let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
+ diag.multipart_suggestion(
+ iter_call.get_suggestion_text(),
+ vec![
+ (stmt.span, String::new()),
+ (iter_call.span, iter_replacement)
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+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 == *{})", s)
++ 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
- &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed),
+use super::NEEDLESS_RANGE_LOOP;
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::has_iter_method;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BinOpKind, BorrowKind, Closure, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::middle::region;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::{sym, Symbol};
+use std::iter::{self, Iterator};
+use std::mem;
+
+/// Checks for looping over a range and then indexing a sequence with it.
+/// The iteratee must be a range literal.
+#[expect(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) {
+ if let Some(higher::Range {
+ start: Some(start),
+ ref end,
+ limits,
+ }) = higher::Range::hir(arg)
+ {
+ // the var must be a single name
+ if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
+ let mut visitor = VarVisitor {
+ cx,
+ var: canonical_id,
+ indexed_mut: FxHashSet::default(),
+ indexed_indirectly: FxHashMap::default(),
+ indexed_directly: FxHashMap::default(),
+ referenced: FxHashSet::default(),
+ nonindex: false,
+ prefer_mutable: false,
+ };
+ walk_expr(&mut visitor, body);
+
+ // linting condition: we only indexed one variable, and indexed it directly
+ if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
+ let (indexed, (indexed_extent, indexed_ty)) = visitor
+ .indexed_directly
+ .into_iter()
+ .next()
+ .expect("already checked that we have exactly 1 element");
+
+ // ensure that the indexed variable was declared before the loop, see #601
+ if let Some(indexed_extent) = indexed_extent {
+ let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id);
+ let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
+ let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
+ if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
+ return;
+ }
+ }
+
+ // don't lint if the container that is indexed does not have .iter() method
+ let has_iter = has_iter_method(cx, indexed_ty);
+ if has_iter.is_none() {
+ return;
+ }
+
+ // don't lint if the container that is indexed into is also used without
+ // indexing
+ if visitor.referenced.contains(&indexed) {
+ return;
+ }
+
+ let starts_at_zero = is_integer_const(cx, start, 0);
+
+ let skip = if starts_at_zero {
+ String::new()
+ } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) {
+ return;
+ } else {
+ format!(".skip({})", snippet(cx, start.span, ".."))
+ };
+
+ let mut end_is_start_plus_val = false;
+
+ let take = if let Some(end) = *end {
+ let mut take_expr = end;
+
+ if let ExprKind::Binary(ref op, left, right) = end.kind {
+ if op.node == BinOpKind::Add {
+ let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
+ let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
+
+ if start_equal_left {
+ take_expr = right;
+ } else if start_equal_right {
+ take_expr = left;
+ }
+
+ end_is_start_plus_val = start_equal_left | start_equal_right;
+ }
+ }
+
+ if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
+ String::new()
+ } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) {
+ return;
+ } else {
+ match limits {
+ ast::RangeLimits::Closed => {
+ let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
+ format!(".take({})", take_expr + sugg::ONE)
+ },
+ ast::RangeLimits::HalfOpen => {
+ format!(".take({})", snippet(cx, take_expr.span, ".."))
+ },
+ }
+ }
+ } else {
+ String::new()
+ };
+
+ let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
+ ("mut ", "iter_mut")
+ } else {
+ ("", "iter")
+ };
+
+ let take_is_empty = take.is_empty();
+ let mut method_1 = take;
+ let mut method_2 = skip;
+
+ if end_is_start_plus_val {
+ mem::swap(&mut method_1, &mut method_2);
+ }
+
+ if visitor.nonindex {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_RANGE_LOOP,
+ arg.span,
- format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2),
++ &format!("the loop variable `{}` is used to index `{indexed}`", ident.name),
+ |diag| {
+ multispan_sugg(
+ diag,
+ "consider using an iterator",
+ vec![
+ (pat.span, format!("({}, <item>)", ident.name)),
+ (
+ arg.span,
- format!("&{}{}", ref_mut, indexed)
++ format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
+ ),
+ ],
+ );
+ },
+ );
+ } else {
+ let repl = if starts_at_zero && take_is_empty {
- format!("{}.{}(){}{}", indexed, method, method_1, method_2)
++ format!("&{ref_mut}{indexed}")
+ } else {
- &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed),
++ format!("{indexed}.{method}(){method_1}{method_2}")
+ };
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_RANGE_LOOP,
+ arg.span,
++ &format!("the loop variable `{}` is only used to index `{indexed}`", ident.name),
+ |diag| {
+ multispan_sugg(
+ diag,
+ "consider using an iterator",
+ vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(method, recv, [], _) = expr.kind;
+ if method.ident.name == sym::len;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind;
+ if path.segments.len() == 1;
+ if path.segments[0].ident.name == var;
+ then {
+ return true;
+ }
+ }
+
+ false
+}
+
+fn is_end_eq_array_len<'tcx>(
+ cx: &LateContext<'tcx>,
+ end: &Expr<'_>,
+ limits: ast::RangeLimits,
+ indexed_ty: Ty<'tcx>,
+) -> bool {
+ if_chain! {
+ if let ExprKind::Lit(ref lit) = end.kind;
+ if let ast::LitKind::Int(end_int, _) = lit.node;
+ if let ty::Array(_, arr_len_const) = indexed_ty.kind();
+ if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env);
+ then {
+ return match limits {
+ ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
+ ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
+ };
+ }
+ }
+
+ false
+}
+
+struct VarVisitor<'a, 'tcx> {
+ /// context reference
+ cx: &'a LateContext<'tcx>,
+ /// var name to look for as index
+ var: HirId,
+ /// indexed variables that are used mutably
+ indexed_mut: FxHashSet<Symbol>,
+ /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global
+ indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>,
+ /// subset of `indexed` of vars that are indexed directly: `v[i]`
+ /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
+ indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
+ /// Any names that are used outside an index operation.
+ /// Used to detect things like `&mut vec` used together with `vec[i]`
+ referenced: FxHashSet<Symbol>,
+ /// has the loop variable been used in expressions other than the index of
+ /// an index op?
+ nonindex: bool,
+ /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar
+ /// takes `&mut self`
+ prefer_mutable: bool,
+}
+
+impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
+ fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
+ if_chain! {
+ // the indexed container is referenced by a name
+ if let ExprKind::Path(ref seqpath) = seqexpr.kind;
+ if let QPath::Resolved(None, seqvar) = *seqpath;
+ if seqvar.segments.len() == 1;
+ if is_local_used(self.cx, idx, self.var);
+ then {
+ if self.prefer_mutable {
+ self.indexed_mut.insert(seqvar.segments[0].ident.name);
+ }
+ let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
+ let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
+ match res {
+ Res::Local(hir_id) => {
+ let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
+ let extent = self.cx
+ .tcx
+ .region_scope_tree(parent_def_id)
+ .var_scope(hir_id.local_id)
+ .unwrap();
+ if index_used_directly {
+ self.indexed_directly.insert(
+ seqvar.segments[0].ident.name,
+ (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
+ );
+ } else {
+ self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
+ }
+ return false; // no need to walk further *on the variable*
+ }
+ Res::Def(DefKind::Static (_)| DefKind::Const, ..) => {
+ if index_used_directly {
+ self.indexed_directly.insert(
+ seqvar.segments[0].ident.name,
+ (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
+ );
+ } else {
+ self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
+ }
+ return false; // no need to walk further *on the variable*
+ }
+ _ => (),
+ }
+ }
+ }
+ true
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // a range index op
+ if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind;
+ if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
+ || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
+ if !self.check(args_1, args_0, expr);
+ then { return }
+ }
+
+ if_chain! {
+ // an index op
+ if let ExprKind::Index(seqexpr, idx) = expr.kind;
+ if !self.check(idx, seqexpr, expr);
+ then { return }
+ }
+
+ if_chain! {
+ // directly using a variable
+ if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
+ if let Res::Local(local_id) = path.res;
+ then {
+ if local_id == self.var {
+ self.nonindex = true;
+ } else {
+ // not the correct variable, but still a variable
+ self.referenced.insert(path.segments[0].ident.name);
+ }
+ }
+ }
+
+ let old = self.prefer_mutable;
+ match expr.kind {
+ ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
+ self.prefer_mutable = true;
+ self.visit_expr(lhs);
+ self.prefer_mutable = false;
+ self.visit_expr(rhs);
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutbl, expr) => {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ self.visit_expr(expr);
+ },
+ ExprKind::Call(f, args) => {
+ self.visit_expr(f);
+ for expr in args {
+ let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
+ self.prefer_mutable = false;
+ if let ty::Ref(_, _, mutbl) = *ty.kind() {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ }
+ self.visit_expr(expr);
+ }
+ },
+ ExprKind::MethodCall(_, receiver, args, _) => {
+ let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ for (ty, expr) in iter::zip(
+ self.cx.tcx.fn_sig(def_id).inputs().skip_binder(),
+ std::iter::once(receiver).chain(args.iter()),
+ ) {
+ self.prefer_mutable = false;
+ if let ty::Ref(_, _, mutbl) = *ty.kind() {
+ if mutbl == Mutability::Mut {
+ self.prefer_mutable = true;
+ }
+ }
+ self.visit_expr(expr);
+ }
+ },
+ ExprKind::Closure(&Closure { body, .. }) => {
+ let body = self.cx.tcx.hir().body(body);
+ self.visit_expr(body.value);
+ },
+ _ => walk_expr(self, expr),
+ }
+ self.prefer_mutable = old;
+ }
+}
--- /dev/null
- fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult {
- match *arg {
+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;
+use rustc_hir::{Block, 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<'_>>,
+) {
+ match never_loop_block(block, 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]
- let mut iter = block.stmts.iter().filter_map(stmt_to_expr).chain(block.expr);
++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,
+ }
+}
+
+fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
- fn never_loop_expr_seq<'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))
- .fold(NeverLoopResult::Otherwise, combine_seq)
++ let mut iter = block
++ .stmts
++ .iter()
++ .filter_map(stmt_to_expr)
++ .chain(block.expr.map(|expr| (expr, None)));
+ never_loop_expr_seq(&mut iter, main_loop_id)
+}
+
- fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
++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)))
++ })
++ .fold(NeverLoopResult::Otherwise, combine_seq)
+}
+
- StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e),
- StmtKind::Local(local) => local.init,
++fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
+ match stmt.kind {
- absorb_break(&never_loop_block(b, main_loop_id))
++ StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some((e, None)),
++ StmtKind::Local(local) => local.init.map(|init| (init, local.els)),
+ StmtKind::Item(..) => None,
+ }
+}
+
+fn never_loop_expr(expr: &Expr<'_>, 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, _)
+ | 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)
+ },
+ ExprKind::Struct(_, fields, base) => {
+ let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), main_loop_id);
+ if let Some(base) = base {
+ combine_both(fields, never_loop_expr(base, main_loop_id))
+ } else {
+ fields
+ }
+ },
+ ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id),
+ ExprKind::Binary(_, e1, e2)
+ | ExprKind::Assign(e1, e2, _)
+ | ExprKind::AssignOp(_, e1, e2)
+ | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().copied(), main_loop_id),
+ ExprKind::Loop(b, _, _, _) => {
+ // Break can come from the inner loop so remove them.
- format!(
- "if let Some({pat}) = {iter}.next()",
- pat = pat_snippet,
- iter = iter_snippet
- )
++ absorb_break(never_loop_block(b, main_loop_id))
+ },
+ ExprKind::If(e, e2, e3) => {
+ 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));
+ combine_seq(e1, combine_branches(e2, e3))
+ },
+ ExprKind::Match(e, arms, _) => {
+ let e = never_loop_expr(e, main_loop_id);
+ if arms.is_empty() {
+ e
+ } else {
+ let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), main_loop_id);
+ combine_seq(e, arms)
+ }
+ },
+ ExprKind::Block(b, _) => never_loop_block(b, main_loop_id),
+ 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
+ }
+ },
+ ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
+ combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
+ }),
+ ExprKind::InlineAsm(asm) => asm
+ .operands
+ .iter()
+ .map(|(o, _)| match o {
+ InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
+ never_loop_expr(expr, main_loop_id)
+ },
+ 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)
+ },
+ 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_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))
+ .fold(NeverLoopResult::Otherwise, combine_both)
+}
+
+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))
+ .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
- &format!(
- "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
- item_str, vec_str, item_str
- ),
+use super::SAME_ITEM_PUSH;
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::path_to_local;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, Stmt, StmtKind};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+use std::iter::Iterator;
+
+/// Detects for loop pushing the same item into a Vec
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ _: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ _: &'tcx Expr<'_>,
+) {
+ fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
+ let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
+ let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
+
+ span_lint_and_help(
+ cx,
+ SAME_ITEM_PUSH,
+ vec.span,
+ "it looks like the same item is being pushed into this Vec",
+ None,
++ &format!("try using vec![{item_str};SIZE] or {vec_str}.resize(NEW_SIZE, {item_str})"),
+ );
+ }
+
+ if !matches!(pat.kind, PatKind::Wild) {
+ return;
+ }
+
+ // Determine whether it is safe to lint the body
+ let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
+ walk_expr(&mut same_item_push_visitor, body);
+ if_chain! {
+ if same_item_push_visitor.should_lint();
+ if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push;
+ let vec_ty = cx.typeck_results().expr_ty(vec);
+ let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
+ if cx
+ .tcx
+ .lang_items()
+ .clone_trait()
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]));
+ then {
+ // Make sure that the push does not involve possibly mutating values
+ match pushed_item.kind {
+ ExprKind::Path(ref qpath) => {
+ match cx.qpath_res(qpath, pushed_item.hir_id) {
+ // immutable bindings that are initialized with literal or constant
+ Res::Local(hir_id) => {
+ let node = cx.tcx.hir().get(hir_id);
+ if_chain! {
+ if let Node::Pat(pat) = node;
+ if let PatKind::Binding(bind_ann, ..) = pat.kind;
+ if !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut));
+ let parent_node = cx.tcx.hir().get_parent_node(hir_id);
+ if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
+ if let Some(init) = parent_let_expr.init;
+ then {
+ match init.kind {
+ // immutable bindings that are initialized with literal
+ ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
+ // immutable bindings that are initialized with constant
+ ExprKind::Path(ref path) => {
+ if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
+ emit_lint(cx, vec, pushed_item);
+ }
+ }
+ _ => {},
+ }
+ }
+ }
+ },
+ // constant
+ Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
+ _ => {},
+ }
+ },
+ ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
+ _ => {},
+ }
+ }
+ }
+}
+
+// Scans the body of the for loop and determines whether lint should be given
+struct SameItemPushVisitor<'a, 'tcx> {
+ non_deterministic_expr: bool,
+ multiple_pushes: bool,
+ // this field holds the last vec push operation visited, which should be the only push seen
+ vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
+ cx: &'a LateContext<'tcx>,
+ used_locals: FxHashSet<HirId>,
+}
+
+impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ non_deterministic_expr: false,
+ multiple_pushes: false,
+ vec_push: None,
+ cx,
+ used_locals: FxHashSet::default(),
+ }
+ }
+
+ fn should_lint(&self) -> bool {
+ if_chain! {
+ if !self.non_deterministic_expr;
+ if !self.multiple_pushes;
+ if let Some((vec, _)) = self.vec_push;
+ if let Some(hir_id) = path_to_local(vec);
+ then {
+ !self.used_locals.contains(&hir_id)
+ } else {
+ false
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ match &expr.kind {
+ // Non-determinism may occur ... don't give a lint
+ ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::If(..) => self.non_deterministic_expr = true,
+ ExprKind::Block(block, _) => self.visit_block(block),
+ _ => {
+ if let Some(hir_id) = path_to_local(expr) {
+ self.used_locals.insert(hir_id);
+ }
+ walk_expr(self, expr);
+ },
+ }
+ }
+
+ fn visit_block(&mut self, b: &'tcx Block<'_>) {
+ for stmt in b.stmts.iter() {
+ self.visit_stmt(stmt);
+ }
+ }
+
+ fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
+ let vec_push_option = get_vec_push(self.cx, s);
+ if vec_push_option.is_none() {
+ // Current statement is not a push so visit inside
+ match &s.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(expr),
+ _ => {},
+ }
+ } else {
+ // Current statement is a push ...check whether another
+ // push had been previously done
+ if self.vec_push.is_none() {
+ self.vec_push = vec_push_option;
+ } else {
+ // There are multiple pushes ... don't lint
+ self.multiple_pushes = true;
+ }
+ }
+ }
+}
+
+// Given some statement, determine if that statement is a push on a Vec. If it is, return
+// the Vec being pushed into and the item being pushed
+fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+ if_chain! {
+ // Extract method being called
+ if let StmtKind::Semi(semi_stmt) = &stmt.kind;
+ if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
+ // Figure out the parameters for the method call
+ if let Some(pushed_item) = args.get(0);
+ // Check that the method being called is push() on a Vec
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
+ if path.ident.name.as_str() == "push";
+ then {
+ return Some((self_expr, pushed_item))
+ }
+ }
+ None
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+use clippy_utils::ty::{has_iter_method, implements_trait};
+use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
+use if_chain::if_chain;
+use rustc_ast::ast::{LitIntType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
+use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{sym, Symbol};
- "{}.{}()",
+use std::iter::Iterator;
+
+#[derive(Debug, PartialEq, Eq)]
+enum IncrementVisitorVarState {
+ Initial, // Not examined yet
+ IncrOnce, // Incremented exactly once, may be a loop counter
+ DontWarn,
+}
+
+/// Scan a for loop for variables that are incremented exactly once and not used after that.
+pub(super) struct IncrementVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ states: HirIdMap<IncrementVisitorVarState>, // incremented variables
+ depth: u32, // depth of conditional expressions
+ done: bool,
+}
+
+impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> {
+ pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ states: HirIdMap::default(),
+ depth: 0,
+ done: false,
+ }
+ }
+
+ pub(super) fn into_results(self) -> impl Iterator<Item = HirId> {
+ self.states.into_iter().filter_map(|(id, state)| {
+ if state == IncrementVisitorVarState::IncrOnce {
+ Some(id)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.done {
+ return;
+ }
+
+ // If node is a variable
+ if let Some(def_id) = path_to_local(expr) {
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial);
+ if *state == IncrementVisitorVarState::IncrOnce {
+ *state = IncrementVisitorVarState::DontWarn;
+ return;
+ }
+
+ match parent.kind {
+ ExprKind::AssignOp(op, lhs, rhs) => {
+ if lhs.hir_id == expr.hir_id {
+ *state = if op.node == BinOpKind::Add
+ && is_integer_const(self.cx, rhs, 1)
+ && *state == IncrementVisitorVarState::Initial
+ && self.depth == 0
+ {
+ IncrementVisitorVarState::IncrOnce
+ } else {
+ // Assigned some other value or assigned multiple times
+ IncrementVisitorVarState::DontWarn
+ };
+ }
+ },
+ ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
+ *state = IncrementVisitorVarState::DontWarn;
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ *state = IncrementVisitorVarState::DontWarn;
+ },
+ _ => (),
+ }
+ }
+
+ walk_expr(self, expr);
+ } else if is_loop(expr) || is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ } else if let ExprKind::Continue(_) = expr.kind {
+ self.done = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+}
+
+enum InitializeVisitorState<'hir> {
+ Initial, // Not examined yet
+ Declared(Symbol, Option<Ty<'hir>>), // Declared but not (yet) initialized
+ Initialized {
+ name: Symbol,
+ ty: Option<Ty<'hir>>,
+ initializer: &'hir Expr<'hir>,
+ },
+ DontWarn,
+}
+
+/// Checks whether a variable is initialized at the start of a loop and not modified
+/// and used after the loop.
+pub(super) struct InitializeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here.
+ var_id: HirId,
+ state: InitializeVisitorState<'tcx>,
+ depth: u32, // depth of conditional expressions
+ past_loop: bool,
+}
+
+impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> {
+ pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self {
+ Self {
+ cx,
+ end_expr,
+ var_id,
+ state: InitializeVisitorState::Initial,
+ depth: 0,
+ past_loop: false,
+ }
+ }
+
+ pub(super) fn get_result(&self) -> Option<(Symbol, Option<Ty<'tcx>>, &'tcx Expr<'tcx>)> {
+ if let InitializeVisitorState::Initialized { name, ty, initializer } = self.state {
+ Some((name, ty, initializer))
+ } else {
+ None
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_local(&mut self, l: &'tcx Local<'_>) {
+ // Look for declarations of the variable
+ if_chain! {
+ if l.pat.hir_id == self.var_id;
+ if let PatKind::Binding(.., ident, _) = l.pat.kind;
+ then {
+ let ty = l.ty.map(|ty| hir_ty_to_ty(self.cx.tcx, ty));
+
+ self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
+ InitializeVisitorState::Initialized {
+ initializer: init,
+ ty,
+ name: ident.name,
+ }
+ })
+ }
+ }
+
+ walk_local(self, l);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if matches!(self.state, InitializeVisitorState::DontWarn) {
+ return;
+ }
+ if expr.hir_id == self.end_expr.hir_id {
+ self.past_loop = true;
+ return;
+ }
+ // No need to visit expressions before the variable is
+ // declared
+ if matches!(self.state, InitializeVisitorState::Initial) {
+ return;
+ }
+
+ // If node is the desired variable, see how it's used
+ if path_to_local_id(expr, self.var_id) {
+ if self.past_loop {
+ self.state = InitializeVisitorState::DontWarn;
+ return;
+ }
+
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ match parent.kind {
+ ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = InitializeVisitorState::DontWarn;
+ },
+ ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = if self.depth == 0 {
+ match self.state {
+ InitializeVisitorState::Declared(name, mut ty) => {
+ if ty.is_none() {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Int(_, LitIntType::Unsuffixed),
+ ..
+ }) = rhs.kind
+ {
+ ty = None;
+ } else {
+ ty = self.cx.typeck_results().expr_ty_opt(rhs);
+ }
+ }
+
+ InitializeVisitorState::Initialized {
+ initializer: rhs,
+ ty,
+ name,
+ }
+ },
+ InitializeVisitorState::Initialized { ty, name, .. } => {
+ InitializeVisitorState::Initialized {
+ initializer: rhs,
+ ty,
+ name,
+ }
+ },
+ _ => InitializeVisitorState::DontWarn,
+ }
+ } else {
+ InitializeVisitorState::DontWarn
+ }
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ self.state = InitializeVisitorState::DontWarn;
+ },
+ _ => (),
+ }
+ }
+
+ walk_expr(self, expr);
+ } else if !self.past_loop && is_loop(expr) {
+ self.state = InitializeVisitorState::DontWarn;
+ } else if is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+fn is_loop(expr: &Expr<'_>) -> bool {
+ matches!(expr.kind, ExprKind::Loop(..))
+}
+
+fn is_conditional(expr: &Expr<'_>) -> bool {
+ matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..))
+}
+
+#[derive(PartialEq, Eq)]
+pub(super) enum Nesting {
+ Unknown, // no nesting detected yet
+ RuledOut, // the iterator is initialized or assigned within scope
+ LookFurther, // no nesting detected, no further walk required
+}
+
+use self::Nesting::{LookFurther, RuledOut, Unknown};
+
+pub(super) struct LoopNestVisitor {
+ pub(super) hir_id: HirId,
+ pub(super) iterator: HirId,
+ pub(super) nesting: Nesting,
+}
+
+impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if stmt.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ } else if self.nesting == Unknown {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if expr.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ return;
+ }
+ match expr.kind {
+ ExprKind::Assign(path, _, _) | ExprKind::AssignOp(_, path, _) => {
+ if path_to_local_id(path, self.iterator) {
+ self.nesting = RuledOut;
+ }
+ },
+ _ => walk_expr(self, expr),
+ }
+ }
+
+ fn visit_pat(&mut self, pat: &'tcx Pat<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if let PatKind::Binding(_, id, ..) = pat.kind {
+ if id == self.iterator {
+ self.nesting = RuledOut;
+ return;
+ }
+ }
+ walk_pat(self, pat);
+ }
+}
+
+/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
+/// actual `Iterator` that the loop uses.
+pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String {
+ let impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
+ implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])
+ });
+ if impls_iterator {
+ format!(
+ "{}",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ )
+ } else {
+ // (&x).into_iter() ==> x.iter()
+ // (&mut x).into_iter() ==> x.iter_mut()
+ let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
+ match &arg_ty.kind() {
+ ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, *inner_ty).is_some() => {
+ let method_name = match mutbl {
+ Mutability::Mut => "iter_mut",
+ Mutability::Not => "iter",
+ };
+ let caller = match &arg.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner,
+ _ => arg,
+ };
+ format!(
- method_name,
++ "{}.{method_name}()",
+ sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(),
+ )
+ },
+ _ => format!(
+ "{}.into_iter()",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ ),
+ }
+ }
+}
--- /dev/null
- get_enclosing_loop_or_multi_call_closure, is_refutable, is_trait_method, match_def_path, paths,
- visitors::is_res_used,
+use super::WHILE_LET_ON_ITERATOR;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{
- use rustc_hir::{def::Res, Closure, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
++ get_enclosing_loop_or_multi_call_closure, is_refutable, is_res_lang_ctor, is_trait_method, visitors::is_res_used,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, Visitor};
- if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = let_pat.kind;
- if let Res::Def(_, pat_did) = pat_path.res;
- if match_def_path(cx, pat_did, &paths::OPTION_SOME);
++use rustc_hir::{def::Res, Closure, Expr, ExprKind, HirId, LangItem, Local, Mutability, PatKind, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter::OnlyBodies;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_span::{symbol::sym, Symbol};
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
+ if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
+ // check for `Some(..)` pattern
- format!("for {} in {}{}", loop_var, iterator, by_ref),
++ if let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind;
++ if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
+ // check for call to `Iterator::next`
+ if let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind;
+ if method_name.ident.name == sym::next;
+ if is_trait_method(cx, let_expr, sym::Iterator);
+ if let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr);
+ // get the loop containing the match expression
+ if !uses_iter(cx, &iter_expr_struct, if_then);
+ then {
+ (let_expr, iter_expr_struct, iter_expr, some_pat, expr)
+ } else {
+ return;
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ let loop_var = if let Some(some_pat) = some_pat.first() {
+ if is_refutable(cx, some_pat) {
+ // Refutable patterns don't work with for loops.
+ return;
+ }
+ snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
+ } else {
+ "_".into()
+ };
+
+ // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
+ // borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
+ // afterwards a mutable borrow of a field isn't necessary.
+ let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
+ || !iter_expr_struct.can_move
+ || !iter_expr_struct.fields.is_empty()
+ || needs_mutable_borrow(cx, &iter_expr_struct, loop_expr)
+ {
+ ".by_ref()"
+ } else {
+ ""
+ };
+
+ let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ WHILE_LET_ON_ITERATOR,
+ expr.span.with_hi(scrutinee_expr.span.hi()),
+ "this loop could be written as a `for` loop",
+ "try",
++ format!("for {loop_var} in {iterator}{by_ref}"),
+ applicability,
+ );
+}
+
+#[derive(Debug)]
+struct IterExpr {
+ /// The fields used, in order of child to parent.
+ fields: Vec<Symbol>,
+ /// The path being used.
+ path: Res,
+ /// Whether or not the iterator can be moved.
+ can_move: bool,
+}
+
+/// Parses any expression to find out which field of which variable is used. Will return `None` if
+/// the expression might have side effects.
+fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option<IterExpr> {
+ let mut fields = Vec::new();
+ let mut can_move = true;
+ loop {
+ if cx
+ .typeck_results()
+ .expr_adjustments(e)
+ .iter()
+ .any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
+ {
+ // Custom deref impls need to borrow the whole value as it's captured by reference
+ can_move = false;
+ fields.clear();
+ }
+ match e.kind {
+ ExprKind::Path(ref path) => {
+ break Some(IterExpr {
+ fields,
+ path: cx.qpath_res(path, e.hir_id),
+ can_move,
+ });
+ },
+ ExprKind::Field(base, name) => {
+ fields.push(name.name);
+ e = base;
+ },
+ // Dereferencing a pointer has no side effects and doesn't affect which field is being used.
+ ExprKind::Unary(UnOp::Deref, base) if cx.typeck_results().expr_ty(base).is_ref() => e = base,
+
+ // Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have
+ // already been seen.
+ ExprKind::Index(base, idx) if !idx.can_have_side_effects() => {
+ can_move = false;
+ fields.clear();
+ e = base;
+ },
+ ExprKind::Unary(UnOp::Deref, base) => {
+ can_move = false;
+ fields.clear();
+ e = base;
+ },
+
+ // No effect and doesn't affect which field is being used.
+ ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base,
+ _ => break None,
+ }
+ }
+}
+
+fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool {
+ loop {
+ match (&e.kind, fields) {
+ (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => {
+ e = base;
+ fields = tail_fields;
+ },
+ (ExprKind::Path(path), []) => {
+ break cx.qpath_res(path, e.hir_id) == path_res;
+ },
+ (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base,
+ _ => break false,
+ }
+ }
+}
+
+/// Checks if the given expression is the same field as, is a child of, or is the parent of the
+/// given field. Used to check if the expression can be used while the given field is borrowed
+/// mutably. e.g. if checking for `x.y`, then `x.y`, `x.y.z`, and `x` will all return true, but
+/// `x.z`, and `y` will return false.
+fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool {
+ match expr.kind {
+ ExprKind::Field(base, name) => {
+ if let Some((head_field, tail_fields)) = fields.split_first() {
+ if name.name == *head_field && is_expr_same_field(cx, base, tail_fields, path_res) {
+ return true;
+ }
+ // Check if the expression is a parent field
+ let mut fields_iter = tail_fields.iter();
+ while let Some(field) = fields_iter.next() {
+ if *field == name.name && is_expr_same_field(cx, base, fields_iter.as_slice(), path_res) {
+ return true;
+ }
+ }
+ }
+
+ // Check if the expression is a child field.
+ let mut e = base;
+ loop {
+ match e.kind {
+ ExprKind::Field(..) if is_expr_same_field(cx, e, fields, path_res) => break true,
+ ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
+ ExprKind::Path(ref path) if fields.is_empty() => {
+ break cx.qpath_res(path, e.hir_id) == path_res;
+ },
+ _ => break false,
+ }
+ }
+ },
+ // If the path matches, this is either an exact match, or the expression is a parent of the field.
+ ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res,
+ ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => {
+ is_expr_same_child_or_parent_field(cx, base, fields, path_res)
+ },
+ _ => false,
+ }
+}
+
+/// Strips off all field and path expressions. This will return true if a field or path has been
+/// skipped. Used to skip them after failing to check for equality.
+fn skip_fields_and_path<'tcx>(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) {
+ let mut e = expr;
+ let e = loop {
+ match e.kind {
+ ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
+ ExprKind::Path(_) => return (None, true),
+ _ => break e,
+ }
+ };
+ (Some(e), e.hir_id != expr.hir_id)
+}
+
+/// Checks if the given expression uses the iterator.
+fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ iter_expr: &'b IterExpr,
+ uses_iter: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.uses_iter {
+ // return
+ } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
+ self.uses_iter = true;
+ } else if let (e, true) = skip_fields_and_path(e) {
+ if let Some(e) = e {
+ self.visit_expr(e);
+ }
+ } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
+ if is_res_used(self.cx, self.iter_expr.path, id) {
+ self.uses_iter = true;
+ }
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ let mut v = V {
+ cx,
+ iter_expr,
+ uses_iter: false,
+ };
+ v.visit_expr(container);
+ v.uses_iter
+}
+
+#[expect(clippy::too_many_lines)]
+fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &Expr<'_>) -> bool {
+ struct AfterLoopVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ iter_expr: &'b IterExpr,
+ loop_id: HirId,
+ after_loop: bool,
+ used_iter: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
+ type NestedFilter = OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.used_iter {
+ return;
+ }
+ if self.after_loop {
+ if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
+ self.used_iter = true;
+ } else if let (e, true) = skip_fields_and_path(e) {
+ if let Some(e) = e {
+ self.visit_expr(e);
+ }
+ } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
+ self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
+ } else {
+ walk_expr(self, e);
+ }
+ } else if self.loop_id == e.hir_id {
+ self.after_loop = true;
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ struct NestedLoopVisitor<'a, 'b, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ iter_expr: &'b IterExpr,
+ local_id: HirId,
+ loop_id: HirId,
+ after_loop: bool,
+ found_local: bool,
+ used_after: bool,
+ }
+ impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
+ type NestedFilter = OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_local(&mut self, l: &'tcx Local<'_>) {
+ if !self.after_loop {
+ l.pat.each_binding_or_first(&mut |_, id, _, _| {
+ if id == self.local_id {
+ self.found_local = true;
+ }
+ });
+ }
+ if let Some(e) = l.init {
+ self.visit_expr(e);
+ }
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.used_after {
+ return;
+ }
+ if self.after_loop {
+ if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
+ self.used_after = true;
+ } else if let (e, true) = skip_fields_and_path(e) {
+ if let Some(e) = e {
+ self.visit_expr(e);
+ }
+ } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind {
+ self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
+ } else {
+ walk_expr(self, e);
+ }
+ } else if e.hir_id == self.loop_id {
+ self.after_loop = true;
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) {
+ let local_id = match iter_expr.path {
+ Res::Local(id) => id,
+ _ => return true,
+ };
+ let mut v = NestedLoopVisitor {
+ cx,
+ iter_expr,
+ local_id,
+ loop_id: loop_expr.hir_id,
+ after_loop: false,
+ found_local: false,
+ used_after: false,
+ };
+ v.visit_expr(e);
+ v.used_after || !v.found_local
+ } else {
+ let mut v = AfterLoopVisitor {
+ cx,
+ iter_expr,
+ loop_id: loop_expr.hir_id,
+ after_loop: false,
+ used_iter: false,
+ };
+ v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
+ v.used_iter
+ }
+}
--- /dev/null
- suggestions.push((span, format!("{}::{}", root, path[0]), hir_id));
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::source::snippet;
+use hir::def::{DefKind, Res};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{edition::Edition, sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[macro_use] use...`.
+ ///
+ /// ### Why is this bad?
+ /// Since the Rust 2018 edition you can import
+ /// macro's directly, this is considered idiomatic.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[macro_use]
+ /// use some_macro;
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub MACRO_USE_IMPORTS,
+ pedantic,
+ "#[macro_use] is no longer needed"
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+struct PathAndSpan {
+ path: String,
+ span: Span,
+}
+
+/// `MacroRefData` includes the name of the macro.
+#[derive(Debug, Clone)]
+pub struct MacroRefData {
+ name: String,
+}
+
+impl MacroRefData {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+#[derive(Default)]
+#[expect(clippy::module_name_repetitions)]
+pub struct MacroUseImports {
+ /// the actual import path used and the span of the attribute above it. The value is
+ /// the location, where the lint should be emitted.
+ imports: Vec<(String, Span, hir::HirId)>,
+ /// the span of the macro reference, kept to ensure only one reference is used per macro call.
+ collected: FxHashSet<Span>,
+ mac_refs: Vec<MacroRefData>,
+}
+
+impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]);
+
+impl MacroUseImports {
+ fn push_unique_macro(&mut self, cx: &LateContext<'_>, span: Span) {
+ let call_site = span.source_callsite();
+ let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
+ if span.source_callee().is_some() && !self.collected.contains(&call_site) {
+ let name = if name.contains("::") {
+ name.split("::").last().unwrap().to_string()
+ } else {
+ name.to_string()
+ };
+
+ self.mac_refs.push(MacroRefData::new(name));
+ self.collected.insert(call_site);
+ }
+ }
+
+ fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_>, span: Span) {
+ let call_site = span.source_callsite();
+ let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
+ if span.source_callee().is_some() && !self.collected.contains(&call_site) {
+ self.mac_refs.push(MacroRefData::new(name.to_string()));
+ self.collected.insert(call_site);
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if_chain! {
+ if cx.sess().opts.edition >= Edition::Edition2018;
+ if let hir::ItemKind::Use(path, _kind) = &item.kind;
+ let hir_id = item.hir_id();
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
+ if let Res::Def(DefKind::Mod, id) = path.res;
+ if !id.is_local();
+ then {
+ for kid in cx.tcx.module_children(id).iter() {
+ if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
+ let span = mac_attr.span;
+ let def_path = cx.tcx.def_path_str(mac_id);
+ self.imports.push((def_path, span, hir_id));
+ }
+ }
+ } else {
+ if item.span.from_expansion() {
+ self.push_unique_macro_pat_ty(cx, item.span);
+ }
+ }
+ }
+ }
+ fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+ if attr.span.from_expansion() {
+ self.push_unique_macro(cx, attr.span);
+ }
+ }
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ if expr.span.from_expansion() {
+ self.push_unique_macro(cx, expr.span);
+ }
+ }
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
+ if stmt.span.from_expansion() {
+ self.push_unique_macro(cx, stmt.span);
+ }
+ }
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) {
+ if pat.span.from_expansion() {
+ self.push_unique_macro_pat_ty(cx, pat.span);
+ }
+ }
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) {
+ if ty.span.from_expansion() {
+ self.push_unique_macro_pat_ty(cx, ty.span);
+ }
+ }
+ fn check_crate_post(&mut self, cx: &LateContext<'_>) {
+ let mut used = FxHashMap::default();
+ let mut check_dup = vec![];
+ for (import, span, hir_id) in &self.imports {
+ let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
+
+ if let Some(idx) = found_idx {
+ self.mac_refs.remove(idx);
+ let seg = import.split("::").collect::<Vec<_>>();
+
+ match seg.as_slice() {
+ // an empty path is impossible
+ // a path should always consist of 2 or more segments
+ [] | [_] => return,
+ [root, item] => {
+ if !check_dup.contains(&(*item).to_string()) {
+ used.entry(((*root).to_string(), span, hir_id))
+ .or_insert_with(Vec::new)
+ .push((*item).to_string());
+ check_dup.push((*item).to_string());
+ }
+ },
+ [root, rest @ ..] => {
+ if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) {
+ let filtered = rest
+ .iter()
+ .filter_map(|item| {
+ if check_dup.contains(&(*item).to_string()) {
+ None
+ } else {
+ Some((*item).to_string())
+ }
+ })
+ .collect::<Vec<_>>();
+ used.entry(((*root).to_string(), span, hir_id))
+ .or_insert_with(Vec::new)
+ .push(filtered.join("::"));
+ check_dup.extend(filtered);
+ } else {
+ let rest = rest.to_vec();
+ used.entry(((*root).to_string(), span, hir_id))
+ .or_insert_with(Vec::new)
+ .push(rest.join("::"));
+ check_dup.extend(rest.iter().map(ToString::to_string));
+ }
+ },
+ }
+ }
+ }
+
+ let mut suggestions = vec![];
+ for ((root, span, hir_id), path) in used {
+ if path.len() == 1 {
- suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")), hir_id));
++ suggestions.push((span, format!("{root}::{}", path[0]), hir_id));
+ } else {
- let help = format!("use {};", import);
++ suggestions.push((span, format!("{root}::{{{}}}", path.join(", ")), hir_id));
+ }
+ }
+
+ // If mac_refs is not empty we have encountered an import we could not handle
+ // such as `std::prelude::v1::foo` or some other macro that expands to an import.
+ if self.mac_refs.is_empty() {
+ for (span, import, hir_id) in suggestions {
++ let help = format!("use {import};");
+ span_lint_hir_and_then(
+ cx,
+ MACRO_USE_IMPORTS,
+ *hir_id,
+ *span,
+ "`macro_use` attributes are no longer needed in the Rust 2018 edition",
+ |diag| {
+ diag.span_suggestion(
+ *span,
+ "remove the attribute and import the macro directly, try",
+ help,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint_and_sugg;
++use crate::rustc_lint::LintContext;
++use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::macros::{root_macro_call, FormatArgsExpn};
+use clippy_utils::source::snippet_with_applicability;
- use clippy_utils::{peel_blocks_with_stmt, sugg};
++use clippy_utils::{peel_blocks_with_stmt, span_extract_comment, sugg};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `if`-then-`panic!` that can be replaced with `assert!`.
+ ///
+ /// ### Why is this bad?
+ /// `assert!` is simpler than `if`-then-`panic!`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let sad_people: Vec<&str> = vec![];
+ /// if !sad_people.is_empty() {
+ /// panic!("there are sad people: {:?}", sad_people);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let sad_people: Vec<&str> = vec![];
+ /// assert!(sad_people.is_empty(), "there are sad people: {:?}", sad_people);
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub MANUAL_ASSERT,
+ pedantic,
+ "`panic!` and only a `panic!` in `if`-then statement"
+}
+
+declare_lint_pass!(ManualAssert => [MANUAL_ASSERT]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualAssert {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::If(cond, then, None) = expr.kind;
+ if !matches!(cond.kind, ExprKind::Let(_));
+ if !expr.span.from_expansion();
+ let then = peel_blocks_with_stmt(then);
+ if let Some(macro_call) = root_macro_call(then.span);
+ if cx.tcx.item_name(macro_call.def_id) == sym::panic;
+ if !cx.tcx.sess.source_map().is_multiline(cond.span);
+ if let Some(format_args) = FormatArgsExpn::find_nested(cx, then, macro_call.expn);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let format_args_snip = snippet_with_applicability(cx, format_args.inputs_span(), "..", &mut applicability);
+ let cond = cond.peel_drop_temps();
++ let mut comments = span_extract_comment(cx.sess().source_map(), expr.span);
++ if !comments.is_empty() {
++ comments += "\n";
++ }
+ let (cond, not) = match cond.kind {
+ ExprKind::Unary(UnOp::Not, e) => (e, ""),
+ _ => (cond, "!"),
+ };
+ let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par();
+ let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip});");
- span_lint_and_sugg(
++ // we show to the user the suggestion without the comments, but when applicating the fix, include the comments in the block
++ span_lint_and_then(
+ cx,
+ MANUAL_ASSERT,
+ expr.span,
+ "only a `panic!` in `if`-then statement",
- "try",
- sugg,
- Applicability::MachineApplicable,
++ |diag| {
++ // comments can be noisy, do not show them to the user
++ diag.tool_only_span_suggestion(
++ expr.span.shrink_to_lo(),
++ "add comments back",
++ comments,
++ applicability);
++ diag.span_suggestion(
++ expr.span,
++ "try instead",
++ sugg,
++ applicability);
++ }
++
+ );
+ }
+ }
+ }
+}
--- /dev/null
- let help = format!("make the function `async` and {}", ret_sugg);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_function_call;
+use clippy_utils::paths::FUTURE_FROM_GENERATOR;
+use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound,
+ HirId, IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It checks for manual implementations of `async` functions.
+ ///
+ /// ### Why is this bad?
+ /// It's more idiomatic to use the dedicated syntax.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::future::Future;
+ ///
+ /// fn foo() -> impl Future<Output = i32> { async { 42 } }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// async fn foo() -> i32 { 42 }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MANUAL_ASYNC_FN,
+ style,
+ "manual implementations of `async` functions can be simplified using the dedicated syntax"
+}
+
+declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if_chain! {
+ if let Some(header) = kind.header();
+ if header.asyncness == IsAsync::NotAsync;
+ // Check that this function returns `impl Future`
+ if let FnRetTy::Return(ret_ty) = decl.output;
+ if let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty);
+ if let Some(output) = future_output_ty(trait_ref);
+ if captures_all_lifetimes(decl.inputs, &output_lifetimes);
+ // Check that the body of the function consists of one async block
+ if let ExprKind::Block(block, _) = body.value.kind;
+ if block.stmts.is_empty();
+ if let Some(closure_body) = desugared_async_block(cx, block);
+ then {
+ let header_span = span.with_hi(ret_ty.span.hi());
+
+ span_lint_and_then(
+ cx,
+ MANUAL_ASYNC_FN,
+ header_span,
+ "this function can be simplified using the `async fn` syntax",
+ |diag| {
+ if_chain! {
+ if let Some(header_snip) = snippet_opt(cx, header_span);
+ if let Some(ret_pos) = position_before_rarrow(&header_snip);
+ if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
+ then {
- format!("async {}{}", &header_snip[..ret_pos], ret_snip),
++ let help = format!("make the function `async` and {ret_sugg}");
+ diag.span_suggestion(
+ header_span,
+ &help,
- snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {}", snip)))
++ format!("async {}{ret_snip}", &header_snip[..ret_pos]),
+ Applicability::MachineApplicable
+ );
+
+ let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
+ diag.span_suggestion(
+ block.span,
+ "move the body of the async block to the enclosing function",
+ body_snip,
+ Applicability::MachineApplicable
+ );
+ }
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+fn future_trait_ref<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: &'tcx Ty<'tcx>,
+) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> {
+ if_chain! {
+ if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind;
+ let item = cx.tcx.hir().item(item_id);
+ if let ItemKind::OpaqueTy(opaque) = &item.kind;
+ if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
+ if let GenericBound::Trait(poly, _) = bound {
+ Some(&poly.trait_ref)
+ } else {
+ None
+ }
+ });
+ if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait();
+ then {
+ let output_lifetimes = bounds
+ .iter()
+ .filter_map(|bound| {
+ if let GenericArg::Lifetime(lt) = bound {
+ Some(lt.name)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ return Some((trait_ref, output_lifetimes));
+ }
+ }
+
+ None
+}
+
+fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
+ if_chain! {
+ if let Some(segment) = trait_ref.path.segments.last();
+ if let Some(args) = segment.args;
+ if args.bindings.len() == 1;
+ let binding = &args.bindings[0];
+ if binding.ident.name == sym::Output;
+ if let TypeBindingKind::Equality{term: Term::Ty(output)} = binding.kind;
+ then {
+ return Some(output)
+ }
+ }
+
+ None
+}
+
+fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName]) -> bool {
+ let input_lifetimes: Vec<LifetimeName> = inputs
+ .iter()
+ .filter_map(|ty| {
+ if let TyKind::Rptr(lt, _) = ty.kind {
+ Some(lt.name)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ // The lint should trigger in one of these cases:
+ // - There are no input lifetimes
+ // - There's only one output lifetime bound using `+ '_`
+ // - All input lifetimes are explicitly bound to the output
+ input_lifetimes.is_empty()
+ || (output_lifetimes.len() == 1 && matches!(output_lifetimes[0], LifetimeName::Infer))
+ || input_lifetimes
+ .iter()
+ .all(|in_lt| output_lifetimes.iter().any(|out_lt| in_lt == out_lt))
+}
+
+fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
+ if_chain! {
+ if let Some(block_expr) = block.expr;
+ if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR);
+ if args.len() == 1;
+ if let Expr{kind: ExprKind::Closure(&Closure { body, .. }), ..} = args[0];
+ let closure_body = cx.tcx.hir().body(body);
+ if closure_body.generator_kind == Some(GeneratorKind::Async(AsyncGeneratorKind::Block));
+ then {
+ return Some(closure_body);
+ }
+ }
+
+ None
+}
+
+fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, String)> {
+ match output.kind {
+ TyKind::Tup(tys) if tys.is_empty() => {
+ let sugg = "remove the return type";
+ Some((sugg, String::new()))
+ },
+ _ => {
+ let sugg = "return the output of the future directly";
++ snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {snip}")))
+ },
+ }
+}
--- /dev/null
--- /dev/null
++use itertools::Itertools;
++use rustc_errors::Diagnostic;
++use rustc_hir::{
++ def::Res, Arm, BinOpKind, Block, Expr, ExprKind, Guard, HirId, PatKind, PathSegment, PrimTy, QPath, StmtKind,
++};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty::Ty;
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::{symbol::sym, Span};
++use std::ops::Deref;
++
++use clippy_utils::{
++ diagnostics::{span_lint_and_then, span_lint_hir_and_then},
++ eq_expr_value, get_trait_def_id,
++ higher::If,
++ is_diag_trait_item, is_trait_method, meets_msrv, msrvs, path_res, path_to_local_id, paths, peel_blocks,
++ peel_blocks_with_stmt,
++ sugg::Sugg,
++ ty::implements_trait,
++ visitors::is_const_evaluatable,
++ MaybePath,
++};
++use rustc_errors::Applicability;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Identifies good opportunities for a clamp function from std or core, and suggests using it.
++ ///
++ /// ### Why is this bad?
++ /// clamp is much shorter, easier to read, and doesn't use any control flow.
++ ///
++ /// ### Known issue(s)
++ /// If the clamped variable is NaN this suggestion will cause the code to propagate NaN
++ /// rather than returning either `max` or `min`.
++ ///
++ /// `clamp` functions will panic if `max < min`, `max.is_nan()`, or `min.is_nan()`.
++ /// Some may consider panicking in these situations to be desirable, but it also may
++ /// introduce panicking where there wasn't any before.
++ ///
++ /// ### Examples
++ /// ```rust
++ /// # let (input, min, max) = (0, -2, 1);
++ /// if input > max {
++ /// max
++ /// } else if input < min {
++ /// min
++ /// } else {
++ /// input
++ /// }
++ /// # ;
++ /// ```
++ ///
++ /// ```rust
++ /// # let (input, min, max) = (0, -2, 1);
++ /// input.max(min).min(max)
++ /// # ;
++ /// ```
++ ///
++ /// ```rust
++ /// # let (input, min, max) = (0, -2, 1);
++ /// match input {
++ /// x if x > max => max,
++ /// x if x < min => min,
++ /// x => x,
++ /// }
++ /// # ;
++ /// ```
++ ///
++ /// ```rust
++ /// # let (input, min, max) = (0, -2, 1);
++ /// let mut x = input;
++ /// if x < min { x = min; }
++ /// if x > max { x = max; }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// # let (input, min, max) = (0, -2, 1);
++ /// input.clamp(min, max)
++ /// # ;
++ /// ```
++ #[clippy::version = "1.66.0"]
++ pub MANUAL_CLAMP,
++ complexity,
++ "using a clamp pattern instead of the clamp function"
++}
++impl_lint_pass!(ManualClamp => [MANUAL_CLAMP]);
++
++pub struct ManualClamp {
++ msrv: Option<RustcVersion>,
++}
++
++impl ManualClamp {
++ pub fn new(msrv: Option<RustcVersion>) -> Self {
++ Self { msrv }
++ }
++}
++
++#[derive(Debug)]
++struct ClampSuggestion<'tcx> {
++ params: InputMinMax<'tcx>,
++ span: Span,
++ make_assignment: Option<&'tcx Expr<'tcx>>,
++ hir_with_ignore_attr: Option<HirId>,
++}
++
++#[derive(Debug)]
++struct InputMinMax<'tcx> {
++ input: &'tcx Expr<'tcx>,
++ min: &'tcx Expr<'tcx>,
++ max: &'tcx Expr<'tcx>,
++ is_float: bool,
++}
++
++impl<'tcx> LateLintPass<'tcx> for ManualClamp {
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
++ if !meets_msrv(self.msrv, msrvs::CLAMP) {
++ return;
++ }
++ if !expr.span.from_expansion() {
++ let suggestion = is_if_elseif_else_pattern(cx, expr)
++ .or_else(|| is_max_min_pattern(cx, expr))
++ .or_else(|| is_call_max_min_pattern(cx, expr))
++ .or_else(|| is_match_pattern(cx, expr))
++ .or_else(|| is_if_elseif_pattern(cx, expr));
++ if let Some(suggestion) = suggestion {
++ emit_suggestion(cx, &suggestion);
++ }
++ }
++ }
++
++ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
++ if !meets_msrv(self.msrv, msrvs::CLAMP) {
++ return;
++ }
++ for suggestion in is_two_if_pattern(cx, block) {
++ emit_suggestion(cx, &suggestion);
++ }
++ }
++ extract_msrv_attr!(LateContext);
++}
++
++fn emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggestion<'tcx>) {
++ let ClampSuggestion {
++ params: InputMinMax {
++ input,
++ min,
++ max,
++ is_float,
++ },
++ span,
++ make_assignment,
++ hir_with_ignore_attr,
++ } = suggestion;
++ let input = Sugg::hir(cx, input, "..").maybe_par();
++ let min = Sugg::hir(cx, min, "..");
++ let max = Sugg::hir(cx, max, "..");
++ let semicolon = if make_assignment.is_some() { ";" } else { "" };
++ let assignment = if let Some(assignment) = make_assignment {
++ let assignment = Sugg::hir(cx, assignment, "..");
++ format!("{assignment} = ")
++ } else {
++ String::new()
++ };
++ let suggestion = format!("{assignment}{input}.clamp({min}, {max}){semicolon}");
++ let msg = "clamp-like pattern without using clamp function";
++ let lint_builder = |d: &mut Diagnostic| {
++ d.span_suggestion(*span, "replace with clamp", suggestion, Applicability::MaybeIncorrect);
++ if *is_float {
++ d.note("clamp will panic if max < min, min.is_nan(), or max.is_nan()")
++ .note("clamp returns NaN if the input is NaN");
++ } else {
++ d.note("clamp will panic if max < min");
++ }
++ };
++ if let Some(hir_id) = hir_with_ignore_attr {
++ span_lint_hir_and_then(cx, MANUAL_CLAMP, *hir_id, *span, msg, lint_builder);
++ } else {
++ span_lint_and_then(cx, MANUAL_CLAMP, *span, msg, lint_builder);
++ }
++}
++
++#[derive(Debug, Copy, Clone, Eq, PartialEq)]
++enum TypeClampability {
++ Float,
++ Ord,
++}
++
++impl TypeClampability {
++ fn is_clampable<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<TypeClampability> {
++ if ty.is_floating_point() {
++ Some(TypeClampability::Float)
++ } else if get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[])) {
++ Some(TypeClampability::Ord)
++ } else {
++ None
++ }
++ }
++
++ fn is_float(self) -> bool {
++ matches!(self, TypeClampability::Float)
++ }
++}
++
++/// Targets patterns like
++///
++/// ```
++/// # let (input, min, max) = (0, -3, 12);
++///
++/// if input < min {
++/// min
++/// } else if input > max {
++/// max
++/// } else {
++/// input
++/// }
++/// # ;
++/// ```
++fn is_if_elseif_else_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
++ if let Some(If {
++ cond,
++ then,
++ r#else: Some(else_if),
++ }) = If::hir(expr)
++ && let Some(If {
++ cond: else_if_cond,
++ then: else_if_then,
++ r#else: Some(else_body),
++ }) = If::hir(peel_blocks(else_if))
++ {
++ let params = is_clamp_meta_pattern(
++ cx,
++ &BinaryOp::new(peel_blocks(cond))?,
++ &BinaryOp::new(peel_blocks(else_if_cond))?,
++ peel_blocks(then),
++ peel_blocks(else_if_then),
++ None,
++ )?;
++ // Contents of the else should be the resolved input.
++ if !eq_expr_value(cx, params.input, peel_blocks(else_body)) {
++ return None;
++ }
++ Some(ClampSuggestion {
++ params,
++ span: expr.span,
++ make_assignment: None,
++ hir_with_ignore_attr: None,
++ })
++ } else {
++ None
++ }
++}
++
++/// Targets patterns like
++///
++/// ```
++/// # let (input, min_value, max_value) = (0, -3, 12);
++///
++/// input.max(min_value).min(max_value)
++/// # ;
++/// ```
++fn is_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
++ if let ExprKind::MethodCall(seg_second, receiver, [arg_second], _) = &expr.kind
++ && (cx.typeck_results().expr_ty_adjusted(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord))
++ && let ExprKind::MethodCall(seg_first, input, [arg_first], _) = &receiver.kind
++ && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() || is_trait_method(cx, receiver, sym::Ord))
++ {
++ let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point();
++ let (min, max) = match (seg_first.ident.as_str(), seg_second.ident.as_str()) {
++ ("min", "max") => (arg_second, arg_first),
++ ("max", "min") => (arg_first, arg_second),
++ _ => return None,
++ };
++ Some(ClampSuggestion {
++ params: InputMinMax { input, min, max, is_float },
++ span: expr.span,
++ make_assignment: None,
++ hir_with_ignore_attr: None,
++ })
++ } else {
++ None
++ }
++}
++
++/// Targets patterns like
++///
++/// ```
++/// # let (input, min_value, max_value) = (0, -3, 12);
++/// # use std::cmp::{max, min};
++/// min(max(input, min_value), max_value)
++/// # ;
++/// ```
++fn is_call_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
++ fn segment<'tcx>(cx: &LateContext<'_>, func: &Expr<'tcx>) -> Option<FunctionType<'tcx>> {
++ match func.kind {
++ ExprKind::Path(QPath::Resolved(None, path)) => {
++ let id = path.res.opt_def_id()?;
++ match cx.tcx.get_diagnostic_name(id) {
++ Some(sym::cmp_min) => Some(FunctionType::CmpMin),
++ Some(sym::cmp_max) => Some(FunctionType::CmpMax),
++ _ if is_diag_trait_item(cx, id, sym::Ord) => {
++ Some(FunctionType::OrdOrFloat(path.segments.last().expect("infallible")))
++ },
++ _ => None,
++ }
++ },
++ ExprKind::Path(QPath::TypeRelative(ty, seg)) => {
++ matches!(path_res(cx, ty), Res::PrimTy(PrimTy::Float(_))).then(|| FunctionType::OrdOrFloat(seg))
++ },
++ _ => None,
++ }
++ }
++
++ enum FunctionType<'tcx> {
++ CmpMin,
++ CmpMax,
++ OrdOrFloat(&'tcx PathSegment<'tcx>),
++ }
++
++ fn check<'tcx>(
++ cx: &LateContext<'tcx>,
++ outer_fn: &'tcx Expr<'tcx>,
++ inner_call: &'tcx Expr<'tcx>,
++ outer_arg: &'tcx Expr<'tcx>,
++ span: Span,
++ ) -> Option<ClampSuggestion<'tcx>> {
++ if let ExprKind::Call(inner_fn, [first, second]) = &inner_call.kind
++ && let Some(inner_seg) = segment(cx, inner_fn)
++ && let Some(outer_seg) = segment(cx, outer_fn)
++ {
++ let (input, inner_arg) = match (is_const_evaluatable(cx, first), is_const_evaluatable(cx, second)) {
++ (true, false) => (second, first),
++ (false, true) => (first, second),
++ _ => return None,
++ };
++ let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point();
++ let (min, max) = match (inner_seg, outer_seg) {
++ (FunctionType::CmpMin, FunctionType::CmpMax) => (outer_arg, inner_arg),
++ (FunctionType::CmpMax, FunctionType::CmpMin) => (inner_arg, outer_arg),
++ (FunctionType::OrdOrFloat(first_segment), FunctionType::OrdOrFloat(second_segment)) => {
++ match (first_segment.ident.as_str(), second_segment.ident.as_str()) {
++ ("min", "max") => (outer_arg, inner_arg),
++ ("max", "min") => (inner_arg, outer_arg),
++ _ => return None,
++ }
++ }
++ _ => return None,
++ };
++ Some(ClampSuggestion {
++ params: InputMinMax { input, min, max, is_float },
++ span,
++ make_assignment: None,
++ hir_with_ignore_attr: None,
++ })
++ } else {
++ None
++ }
++ }
++
++ if let ExprKind::Call(outer_fn, [first, second]) = &expr.kind {
++ check(cx, outer_fn, first, second, expr.span).or_else(|| check(cx, outer_fn, second, first, expr.span))
++ } else {
++ None
++ }
++}
++
++/// Targets patterns like
++///
++/// ```
++/// # let (input, min, max) = (0, -3, 12);
++///
++/// match input {
++/// input if input > max => max,
++/// input if input < min => min,
++/// input => input,
++/// }
++/// # ;
++/// ```
++fn is_match_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
++ if let ExprKind::Match(value, [first_arm, second_arm, last_arm], rustc_hir::MatchSource::Normal) = &expr.kind {
++ // Find possible min/max branches
++ let minmax_values = |a: &'tcx Arm<'tcx>| {
++ if let PatKind::Binding(_, var_hir_id, _, None) = &a.pat.kind
++ && let Some(Guard::If(e)) = a.guard {
++ Some((e, var_hir_id, a.body))
++ } else {
++ None
++ }
++ };
++ let (first, first_hir_id, first_expr) = minmax_values(first_arm)?;
++ let (second, second_hir_id, second_expr) = minmax_values(second_arm)?;
++ let first = BinaryOp::new(first)?;
++ let second = BinaryOp::new(second)?;
++ if let PatKind::Binding(_, binding, _, None) = &last_arm.pat.kind
++ && path_to_local_id(peel_blocks_with_stmt(last_arm.body), *binding)
++ && last_arm.guard.is_none()
++ {
++ // Proceed as normal
++ } else {
++ return None;
++ }
++ if let Some(params) = is_clamp_meta_pattern(
++ cx,
++ &first,
++ &second,
++ first_expr,
++ second_expr,
++ Some((*first_hir_id, *second_hir_id)),
++ ) {
++ return Some(ClampSuggestion {
++ params: InputMinMax {
++ input: value,
++ min: params.min,
++ max: params.max,
++ is_float: params.is_float,
++ },
++ span: expr.span,
++ make_assignment: None,
++ hir_with_ignore_attr: None,
++ });
++ }
++ }
++ None
++}
++
++/// Targets patterns like
++///
++/// ```
++/// # let (input, min, max) = (0, -3, 12);
++///
++/// let mut x = input;
++/// if x < min { x = min; }
++/// if x > max { x = max; }
++/// ```
++fn is_two_if_pattern<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Vec<ClampSuggestion<'tcx>> {
++ block_stmt_with_last(block)
++ .tuple_windows()
++ .filter_map(|(maybe_set_first, maybe_set_second)| {
++ if let StmtKind::Expr(first_expr) = *maybe_set_first
++ && let StmtKind::Expr(second_expr) = *maybe_set_second
++ && let Some(If { cond: first_cond, then: first_then, r#else: None }) = If::hir(first_expr)
++ && let Some(If { cond: second_cond, then: second_then, r#else: None }) = If::hir(second_expr)
++ && let ExprKind::Assign(
++ maybe_input_first_path,
++ maybe_min_max_first,
++ _
++ ) = peel_blocks_with_stmt(first_then).kind
++ && let ExprKind::Assign(
++ maybe_input_second_path,
++ maybe_min_max_second,
++ _
++ ) = peel_blocks_with_stmt(second_then).kind
++ && eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path)
++ && let Some(first_bin) = BinaryOp::new(first_cond)
++ && let Some(second_bin) = BinaryOp::new(second_cond)
++ && let Some(input_min_max) = is_clamp_meta_pattern(
++ cx,
++ &first_bin,
++ &second_bin,
++ maybe_min_max_first,
++ maybe_min_max_second,
++ None
++ )
++ {
++ Some(ClampSuggestion {
++ params: InputMinMax {
++ input: maybe_input_first_path,
++ min: input_min_max.min,
++ max: input_min_max.max,
++ is_float: input_min_max.is_float,
++ },
++ span: first_expr.span.to(second_expr.span),
++ make_assignment: Some(maybe_input_first_path),
++ hir_with_ignore_attr: Some(first_expr.hir_id()),
++ })
++ } else {
++ None
++ }
++ })
++ .collect()
++}
++
++/// Targets patterns like
++///
++/// ```
++/// # let (mut input, min, max) = (0, -3, 12);
++///
++/// if input < min {
++/// input = min;
++/// } else if input > max {
++/// input = max;
++/// }
++/// ```
++fn is_if_elseif_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<ClampSuggestion<'tcx>> {
++ if let Some(If {
++ cond,
++ then,
++ r#else: Some(else_if),
++ }) = If::hir(expr)
++ && let Some(If {
++ cond: else_if_cond,
++ then: else_if_then,
++ r#else: None,
++ }) = If::hir(peel_blocks(else_if))
++ && let ExprKind::Assign(
++ maybe_input_first_path,
++ maybe_min_max_first,
++ _
++ ) = peel_blocks_with_stmt(then).kind
++ && let ExprKind::Assign(
++ maybe_input_second_path,
++ maybe_min_max_second,
++ _
++ ) = peel_blocks_with_stmt(else_if_then).kind
++ {
++ let params = is_clamp_meta_pattern(
++ cx,
++ &BinaryOp::new(peel_blocks(cond))?,
++ &BinaryOp::new(peel_blocks(else_if_cond))?,
++ peel_blocks(maybe_min_max_first),
++ peel_blocks(maybe_min_max_second),
++ None,
++ )?;
++ if !eq_expr_value(cx, maybe_input_first_path, maybe_input_second_path) {
++ return None;
++ }
++ Some(ClampSuggestion {
++ params,
++ span: expr.span,
++ make_assignment: Some(maybe_input_first_path),
++ hir_with_ignore_attr: None,
++ })
++ } else {
++ None
++ }
++}
++
++/// `ExprKind::Binary` but more narrowly typed
++#[derive(Debug, Clone, Copy)]
++struct BinaryOp<'tcx> {
++ op: BinOpKind,
++ left: &'tcx Expr<'tcx>,
++ right: &'tcx Expr<'tcx>,
++}
++
++impl<'tcx> BinaryOp<'tcx> {
++ fn new(e: &'tcx Expr<'tcx>) -> Option<BinaryOp<'tcx>> {
++ match &e.kind {
++ ExprKind::Binary(op, left, right) => Some(BinaryOp {
++ op: op.node,
++ left,
++ right,
++ }),
++ _ => None,
++ }
++ }
++
++ fn flip(&self) -> Self {
++ Self {
++ op: match self.op {
++ BinOpKind::Le => BinOpKind::Ge,
++ BinOpKind::Lt => BinOpKind::Gt,
++ BinOpKind::Ge => BinOpKind::Le,
++ BinOpKind::Gt => BinOpKind::Lt,
++ other => other,
++ },
++ left: self.right,
++ right: self.left,
++ }
++ }
++}
++
++/// The clamp meta pattern is a pattern shared between many (but not all) patterns.
++/// In summary, this pattern consists of two if statements that meet many criteria,
++/// - binary operators that are one of [`>`, `<`, `>=`, `<=`].
++/// - Both binary statements must have a shared argument
++/// - Which can appear on the left or right side of either statement
++/// - The binary operators must define a finite range for the shared argument. To put this in
++/// the terms of Rust `std` library, the following ranges are acceptable
++/// - `Range`
++/// - `RangeInclusive`
++/// And all other range types are not accepted. For the purposes of `clamp` it's irrelevant
++/// whether the range is inclusive or not, the output is the same.
++/// - The result of each if statement must be equal to the argument unique to that if statement. The
++/// result can not be the shared argument in either case.
++fn is_clamp_meta_pattern<'tcx>(
++ cx: &LateContext<'tcx>,
++ first_bin: &BinaryOp<'tcx>,
++ second_bin: &BinaryOp<'tcx>,
++ first_expr: &'tcx Expr<'tcx>,
++ second_expr: &'tcx Expr<'tcx>,
++ // This parameters is exclusively for the match pattern.
++ // It exists because the variable bindings used in that pattern
++ // refer to the variable bound in the match arm, not the variable
++ // bound outside of it. Fortunately due to context we know this has to
++ // be the input variable, not the min or max.
++ input_hir_ids: Option<(HirId, HirId)>,
++) -> Option<InputMinMax<'tcx>> {
++ fn check<'tcx>(
++ cx: &LateContext<'tcx>,
++ first_bin: &BinaryOp<'tcx>,
++ second_bin: &BinaryOp<'tcx>,
++ first_expr: &'tcx Expr<'tcx>,
++ second_expr: &'tcx Expr<'tcx>,
++ input_hir_ids: Option<(HirId, HirId)>,
++ is_float: bool,
++ ) -> Option<InputMinMax<'tcx>> {
++ match (&first_bin.op, &second_bin.op) {
++ (BinOpKind::Ge | BinOpKind::Gt, BinOpKind::Le | BinOpKind::Lt) => {
++ let (min, max) = (second_expr, first_expr);
++ let refers_to_input = match input_hir_ids {
++ Some((first_hir_id, second_hir_id)) => {
++ path_to_local_id(peel_blocks(first_bin.left), first_hir_id)
++ && path_to_local_id(peel_blocks(second_bin.left), second_hir_id)
++ },
++ None => eq_expr_value(cx, first_bin.left, second_bin.left),
++ };
++ (refers_to_input
++ && eq_expr_value(cx, first_bin.right, first_expr)
++ && eq_expr_value(cx, second_bin.right, second_expr))
++ .then_some(InputMinMax {
++ input: first_bin.left,
++ min,
++ max,
++ is_float,
++ })
++ },
++ _ => None,
++ }
++ }
++ // First filter out any expressions with side effects
++ let exprs = [
++ first_bin.left,
++ first_bin.right,
++ second_bin.left,
++ second_bin.right,
++ first_expr,
++ second_expr,
++ ];
++ let clampability = TypeClampability::is_clampable(cx, cx.typeck_results().expr_ty(first_expr))?;
++ let is_float = clampability.is_float();
++ if exprs.iter().any(|e| peel_blocks(e).can_have_side_effects()) {
++ return None;
++ }
++ if !(is_ord_op(first_bin.op) && is_ord_op(second_bin.op)) {
++ return None;
++ }
++ let cases = [
++ (*first_bin, *second_bin),
++ (first_bin.flip(), second_bin.flip()),
++ (first_bin.flip(), *second_bin),
++ (*first_bin, second_bin.flip()),
++ ];
++
++ cases.into_iter().find_map(|(first, second)| {
++ check(cx, &first, &second, first_expr, second_expr, input_hir_ids, is_float).or_else(|| {
++ check(
++ cx,
++ &second,
++ &first,
++ second_expr,
++ first_expr,
++ input_hir_ids.map(|(l, r)| (r, l)),
++ is_float,
++ )
++ })
++ })
++}
++
++fn block_stmt_with_last<'tcx>(block: &'tcx Block<'tcx>) -> impl Iterator<Item = MaybeBorrowedStmtKind<'tcx>> {
++ block
++ .stmts
++ .iter()
++ .map(|s| MaybeBorrowedStmtKind::Borrowed(&s.kind))
++ .chain(
++ block
++ .expr
++ .as_ref()
++ .map(|e| MaybeBorrowedStmtKind::Owned(StmtKind::Expr(e))),
++ )
++}
++
++fn is_ord_op(op: BinOpKind) -> bool {
++ matches!(op, BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt)
++}
++
++/// Really similar to Cow, but doesn't have a `Clone` requirement.
++#[derive(Debug)]
++enum MaybeBorrowedStmtKind<'a> {
++ Borrowed(&'a StmtKind<'a>),
++ Owned(StmtKind<'a>),
++}
++
++impl<'a> Clone for MaybeBorrowedStmtKind<'a> {
++ fn clone(&self) -> Self {
++ match self {
++ Self::Borrowed(t) => Self::Borrowed(t),
++ Self::Owned(StmtKind::Expr(e)) => Self::Owned(StmtKind::Expr(e)),
++ Self::Owned(_) => unreachable!("Owned should only ever contain a StmtKind::Expr."),
++ }
++ }
++}
++
++impl<'a> Deref for MaybeBorrowedStmtKind<'a> {
++ type Target = StmtKind<'a>;
++
++ fn deref(&self) -> &Self::Target {
++ match self {
++ Self::Borrowed(t) => t,
++ Self::Owned(t) => t,
++ }
++ }
++}
--- /dev/null
- format!("#[non_exhaustive] {}", snippet),
+use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::{is_doc_hidden, meets_msrv, msrvs};
+use rustc_ast::ast::{self, VisibilityKind};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{self as hir, Expr, ExprKind, QPath};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::ty::DefIdTree;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::{DefId, LocalDefId};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of the non-exhaustive pattern.
+ ///
+ /// ### Why is this bad?
+ /// Using the #[non_exhaustive] attribute expresses better the intent
+ /// and allows possible optimizations when applied to enums.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S {
+ /// pub a: i32,
+ /// pub b: i32,
+ /// _c: (),
+ /// }
+ ///
+ /// enum E {
+ /// A,
+ /// B,
+ /// #[doc(hidden)]
+ /// _C,
+ /// }
+ ///
+ /// struct T(pub i32, pub i32, ());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// struct S {
+ /// pub a: i32,
+ /// pub b: i32,
+ /// }
+ ///
+ /// #[non_exhaustive]
+ /// enum E {
+ /// A,
+ /// B,
+ /// }
+ ///
+ /// #[non_exhaustive]
+ /// struct T(pub i32, pub i32);
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub MANUAL_NON_EXHAUSTIVE,
+ style,
+ "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
+}
+
+#[expect(clippy::module_name_repetitions)]
+pub struct ManualNonExhaustiveStruct {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualNonExhaustiveStruct {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
+
+#[expect(clippy::module_name_repetitions)]
+pub struct ManualNonExhaustiveEnum {
+ msrv: Option<RustcVersion>,
+ constructed_enum_variants: FxHashSet<(DefId, DefId)>,
+ potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
+}
+
+impl ManualNonExhaustiveEnum {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ constructed_enum_variants: FxHashSet::default(),
+ potential_enums: Vec::new(),
+ }
+ }
+}
+
+impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
+
+impl EarlyLintPass for ManualNonExhaustiveStruct {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
+ return;
+ }
+
+ if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
+ let (fields, delimiter) = match variant_data {
+ ast::VariantData::Struct(fields, _) => (&**fields, '{'),
+ ast::VariantData::Tuple(fields, _) => (&**fields, '('),
+ ast::VariantData::Unit(_) => return,
+ };
+ if fields.len() <= 1 {
+ return;
+ }
+ let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
+ VisibilityKind::Public => None,
+ VisibilityKind::Inherited => Some(Ok(f)),
+ VisibilityKind::Restricted { .. } => Some(Err(())),
+ });
+ if let Some(Ok(field)) = iter.next()
+ && iter.next().is_none()
+ && field.ty.kind.is_unit()
+ && field.ident.map_or(true, |name| name.as_str().starts_with('_'))
+ {
+ span_lint_and_then(
+ cx,
+ MANUAL_NON_EXHAUSTIVE,
+ item.span,
+ "this seems like a manual implementation of the non-exhaustive pattern",
+ |diag| {
+ if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
+ && let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
+ && let Some(snippet) = snippet_opt(cx, header_span)
+ {
+ diag.span_suggestion(
+ header_span,
+ "add the attribute",
- format!("#[non_exhaustive] {}", snippet),
++ format!("#[non_exhaustive] {snippet}"),
+ Applicability::Unspecified,
+ );
+ }
+ diag.span_help(field.span, "remove this field");
+ }
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
+ return;
+ }
+
+ if let hir::ItemKind::Enum(def, _) = &item.kind
+ && def.variants.len() > 1
+ {
+ let mut iter = def.variants.iter().filter_map(|v| {
+ let id = cx.tcx.hir().local_def_id(v.id);
+ (matches!(v.data, hir::VariantData::Unit(_))
+ && v.ident.as_str().starts_with('_')
+ && is_doc_hidden(cx.tcx.hir().attrs(v.id)))
+ .then_some((id, v.span))
+ });
+ if let Some((id, span)) = iter.next()
+ && iter.next().is_none()
+ {
+ self.potential_enums.push((item.def_id.def_id, id, item.span, span));
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
+ && let [.., name] = p.segments
+ && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
+ && name.ident.as_str().starts_with('_')
+ {
+ let variant_id = cx.tcx.parent(id);
+ let enum_id = cx.tcx.parent(variant_id);
+
+ self.constructed_enum_variants.insert((enum_id, variant_id));
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ for &(enum_id, _, enum_span, variant_span) in
+ self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
+ !self
+ .constructed_enum_variants
+ .contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
+ })
+ {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(enum_id);
+ span_lint_hir_and_then(
+ cx,
+ MANUAL_NON_EXHAUSTIVE,
+ hir_id,
+ enum_span,
+ "this seems like a manual implementation of the non-exhaustive pattern",
+ |diag| {
+ if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive()
+ && let header_span = cx.sess().source_map().span_until_char(enum_span, '{')
+ && let Some(snippet) = snippet_opt(cx, header_span)
+ {
+ diag.span_suggestion(
+ header_span,
+ "add the attribute",
++ format!("#[non_exhaustive] {snippet}"),
+ Applicability::Unspecified,
+ );
+ }
+ diag.span_help(variant_span, "remove this variant");
+ },
+ );
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- #[clippy::version = "1.63.0"]
+use clippy_utils::consts::{constant_full_int, FullInt};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{in_constant, meets_msrv, msrvs, path_to_local};
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
+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 an expression like `((x % 4) + 4) % 4` which is a common manual reimplementation
+ /// of `x.rem_euclid(4)`.
+ ///
+ /// ### Why is this bad?
+ /// It's simpler and more readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = 24;
+ /// let rem = ((x % 4) + 4) % 4;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x: i32 = 24;
+ /// let rem = x.rem_euclid(4);
+ /// ```
++ #[clippy::version = "1.64.0"]
+ pub MANUAL_REM_EUCLID,
+ complexity,
+ "manually reimplementing `rem_euclid`"
+}
+
+pub struct ManualRemEuclid {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualRemEuclid {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !meets_msrv(self.msrv, msrvs::REM_EUCLID) {
+ return;
+ }
+
+ if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, msrvs::REM_EUCLID_CONST) {
+ return;
+ }
+
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Binary(op1, expr1, right) = expr.kind
+ && op1.node == BinOpKind::Rem
+ && let Some(const1) = check_for_unsigned_int_constant(cx, right)
+ && let ExprKind::Binary(op2, left, right) = expr1.kind
+ && op2.node == BinOpKind::Add
+ && let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right)
+ && let ExprKind::Binary(op3, expr3, right) = expr2.kind
+ && op3.node == BinOpKind::Rem
+ && let Some(const3) = check_for_unsigned_int_constant(cx, right)
+ // Also ensures the const is nonzero since zero can't be a divisor
+ && const1 == const2 && const2 == const3
+ && let Some(hir_id) = path_to_local(expr3)
+ && let Some(Node::Pat(_)) = cx.tcx.hir().find(hir_id) {
+ // Apply only to params or locals with annotated types
+ match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ Some(Node::Param(..)) => (),
+ Some(Node::Local(local)) => {
+ let Some(ty) = local.ty else { return };
+ if matches!(ty.kind, TyKind::Infer) {
+ return;
+ }
+ }
+ _ => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app);
+ span_lint_and_sugg(
+ cx,
+ MANUAL_REM_EUCLID,
+ expr.span,
+ "manual `rem_euclid` implementation",
+ "consider using",
+ format!("{rem_of}.rem_euclid({const1})"),
+ app,
+ );
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+// Checks if either the left or right expressions can be an unsigned int constant and returns that
+// constant along with the other expression unchanged if so
+fn check_for_either_unsigned_int_constant<'a>(
+ cx: &'a LateContext<'_>,
+ left: &'a Expr<'_>,
+ right: &'a Expr<'_>,
+) -> Option<(u128, &'a Expr<'a>)> {
+ check_for_unsigned_int_constant(cx, left)
+ .map(|int_const| (int_const, right))
+ .or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left)))
+}
+
+fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
+ let Some(int_const) = constant_full_int(cx, cx.typeck_results(), expr) else { return None };
+ match int_const {
+ FullInt::S(s) => s.try_into().ok(),
+ FullInt::U(u) => Some(u),
+ }
+}
--- /dev/null
- #[clippy::version = "1.63.0"]
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq};
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_hir::ExprKind::Assign;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+
+const ACCEPTABLE_METHODS: [&[&str]; 4] = [
+ &paths::HASHSET_ITER,
+ &paths::BTREESET_ITER,
+ &paths::SLICE_INTO,
+ &paths::VEC_DEQUE_ITER,
+];
+const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 6] = [
+ (sym::BTreeSet, Some(msrvs::BTREE_SET_RETAIN)),
+ (sym::BTreeMap, Some(msrvs::BTREE_MAP_RETAIN)),
+ (sym::HashSet, Some(msrvs::HASH_SET_RETAIN)),
+ (sym::HashMap, Some(msrvs::HASH_MAP_RETAIN)),
+ (sym::Vec, None),
+ (sym::VecDeque, None),
+];
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for code to be replaced by `.retain()`.
+ /// ### Why is this bad?
+ /// `.retain()` is simpler and avoids needless allocation.
+ /// ### Example
+ /// ```rust
+ /// let mut vec = vec![0, 1, 2];
+ /// vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect();
+ /// vec = vec.into_iter().filter(|x| x % 2 == 0).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let mut vec = vec![0, 1, 2];
+ /// vec.retain(|x| x % 2 == 0);
+ /// ```
- && match_def_path(cx, into_iter_def_id, &paths::CORE_ITER_INTO_ITER)
++ #[clippy::version = "1.64.0"]
+ pub MANUAL_RETAIN,
+ perf,
+ "`retain()` is simpler and the same functionalitys"
+}
+
+pub struct ManualRetain {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualRetain {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualRetain {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if let Some(parent_expr) = get_parent_expr(cx, expr)
+ && let Assign(left_expr, collect_expr, _) = &parent_expr.kind
+ && let hir::ExprKind::MethodCall(seg, ..) = &collect_expr.kind
+ && seg.args.is_none()
+ && let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind
+ && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
+ && match_def_path(cx, collect_def_id, &paths::CORE_ITER_COLLECT) {
+ check_into_iter(cx, parent_expr, left_expr, target_expr, self.msrv);
+ check_iter(cx, parent_expr, left_expr, target_expr, self.msrv);
+ check_to_owned(cx, parent_expr, left_expr, target_expr, self.msrv);
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn check_into_iter(
+ cx: &LateContext<'_>,
+ parent_expr: &hir::Expr<'_>,
+ left_expr: &hir::Expr<'_>,
+ target_expr: &hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
+ && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
+ && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER)
+ && let hir::ExprKind::MethodCall(_, struct_expr, [], _) = &into_iter_expr.kind
+ && let Some(into_iter_def_id) = cx.typeck_results().type_dependent_def_id(into_iter_expr.hir_id)
- Some(format!("{}.retain(|{}| {})", snippet(cx, left_expr.span, ".."), filter_param_ident, snippet(cx, filter_body.value.span, "..")))
++ && cx.tcx.lang_items().require(hir::LangItem::IntoIterIntoIter).ok() == Some(into_iter_def_id)
+ && match_acceptable_type(cx, left_expr, msrv)
+ && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) {
+ suggest(cx, parent_expr, left_expr, target_expr);
+ }
+}
+
+fn check_iter(
+ cx: &LateContext<'_>,
+ parent_expr: &hir::Expr<'_>,
+ left_expr: &hir::Expr<'_>,
+ target_expr: &hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
+ && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
+ && (match_def_path(cx, copied_def_id, &paths::CORE_ITER_COPIED)
+ || match_def_path(cx, copied_def_id, &paths::CORE_ITER_CLONED))
+ && let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind
+ && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id)
+ && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER)
+ && let hir::ExprKind::MethodCall(_, struct_expr, [], _) = &iter_expr.kind
+ && let Some(iter_expr_def_id) = cx.typeck_results().type_dependent_def_id(iter_expr.hir_id)
+ && match_acceptable_def_path(cx, iter_expr_def_id)
+ && match_acceptable_type(cx, left_expr, msrv)
+ && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) {
+ suggest(cx, parent_expr, left_expr, filter_expr);
+ }
+}
+
+fn check_to_owned(
+ cx: &LateContext<'_>,
+ parent_expr: &hir::Expr<'_>,
+ left_expr: &hir::Expr<'_>,
+ target_expr: &hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if meets_msrv(msrv, msrvs::STRING_RETAIN)
+ && let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
+ && let Some(to_owned_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
+ && match_def_path(cx, to_owned_def_id, &paths::TO_OWNED_METHOD)
+ && let hir::ExprKind::MethodCall(_, chars_expr, [_], _) = &filter_expr.kind
+ && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id)
+ && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER)
+ && let hir::ExprKind::MethodCall(_, str_expr, [], _) = &chars_expr.kind
+ && let Some(chars_expr_def_id) = cx.typeck_results().type_dependent_def_id(chars_expr.hir_id)
+ && match_def_path(cx, chars_expr_def_id, &paths::STR_CHARS)
+ && let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
+ && is_type_diagnostic_item(cx, ty, sym::String)
+ && SpanlessEq::new(cx).eq_expr(left_expr, str_expr) {
+ suggest(cx, parent_expr, left_expr, filter_expr);
+ }
+}
+
+fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) {
+ if let hir::ExprKind::MethodCall(_, _, [closure], _) = filter_expr.kind
+ && let hir::ExprKind::Closure(&hir::Closure { body, ..}) = closure.kind
+ && let filter_body = cx.tcx.hir().body(body)
+ && let [filter_params] = filter_body.params
+ && let Some(sugg) = match filter_params.pat.kind {
+ hir::PatKind::Binding(_, _, filter_param_ident, None) => {
- Some(format!("{}.retain(|{}| {})", snippet(cx, left_expr.span, ".."), filter_param_ident, snippet(cx, filter_body.value.span, "..")))
++ Some(format!("{}.retain(|{filter_param_ident}| {})", snippet(cx, left_expr.span, ".."), snippet(cx, filter_body.value.span, "..")))
+ },
+ hir::PatKind::Tuple([key_pat, value_pat], _) => {
+ make_sugg(cx, key_pat, value_pat, left_expr, filter_body)
+ },
+ hir::PatKind::Ref(pat, _) => {
+ match pat.kind {
+ hir::PatKind::Binding(_, _, filter_param_ident, None) => {
- "{}.retain(|{}, &mut {}| {})",
++ Some(format!("{}.retain(|{filter_param_ident}| {})", snippet(cx, left_expr.span, ".."), snippet(cx, filter_body.value.span, "..")))
+ },
+ _ => None
+ }
+ },
+ _ => None
+ } {
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RETAIN,
+ parent_expr.span,
+ "this expression can be written more simply using `.retain()`",
+ "consider calling `.retain()` instead",
+ sugg,
+ Applicability::MachineApplicable
+ );
+ }
+}
+
+fn make_sugg(
+ cx: &LateContext<'_>,
+ key_pat: &rustc_hir::Pat<'_>,
+ value_pat: &rustc_hir::Pat<'_>,
+ left_expr: &hir::Expr<'_>,
+ filter_body: &hir::Body<'_>,
+) -> Option<String> {
+ match (&key_pat.kind, &value_pat.kind) {
+ (hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Binding(_, _, value_param_ident, None)) => {
+ Some(format!(
- key_param_ident,
- value_param_ident,
++ "{}.retain(|{key_param_ident}, &mut {value_param_ident}| {})",
+ snippet(cx, left_expr.span, ".."),
- "{}.retain(|{}, _| {})",
+ snippet(cx, filter_body.value.span, "..")
+ ))
+ },
+ (hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Wild) => Some(format!(
- key_param_ident,
++ "{}.retain(|{key_param_ident}, _| {})",
+ snippet(cx, left_expr.span, ".."),
- "{}.retain(|_, &mut {}| {})",
+ snippet(cx, filter_body.value.span, "..")
+ )),
+ (hir::PatKind::Wild, hir::PatKind::Binding(_, _, value_param_ident, None)) => Some(format!(
- value_param_ident,
++ "{}.retain(|_, &mut {value_param_ident}| {})",
+ snippet(cx, left_expr.span, ".."),
+ snippet(cx, filter_body.value.span, "..")
+ )),
+ _ => None,
+ }
+}
+
+fn match_acceptable_def_path(cx: &LateContext<'_>, collect_def_id: DefId) -> bool {
+ ACCEPTABLE_METHODS
+ .iter()
+ .any(|&method| match_def_path(cx, collect_def_id, method))
+}
+
+fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Option<RustcVersion>) -> bool {
+ let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
+ ACCEPTABLE_TYPES.iter().any(|(ty, acceptable_msrv)| {
+ is_type_diagnostic_item(cx, expr_ty, *ty)
+ && acceptable_msrv.map_or(true, |acceptable_msrv| meets_msrv(msrv, acceptable_msrv))
+ })
+}
--- /dev/null
- span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {} manually", kind_word), |diag| {
- diag.span_note(test_span, &format!("the {} was tested here", kind_word));
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{eq_expr_value, higher, match_def_path, meets_msrv, msrvs, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_hir::def::Res;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::BinOpKind;
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using
+ /// the pattern's length.
+ ///
+ /// ### Why is this bad?
+ /// Using `str:strip_{prefix,suffix}` is safer and may have better performance as there is no
+ /// slicing which may panic and the compiler does not need to insert this panic code. It is
+ /// also sometimes more readable as it removes the need for duplicating or storing the pattern
+ /// used by `str::{starts,ends}_with` and in the slicing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s = "hello, world!";
+ /// if s.starts_with("hello, ") {
+ /// assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let s = "hello, world!";
+ /// if let Some(end) = s.strip_prefix("hello, ") {
+ /// assert_eq!(end.to_uppercase(), "WORLD!");
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub MANUAL_STRIP,
+ complexity,
+ "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing"
+}
+
+pub struct ManualStrip {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualStrip {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualStrip => [MANUAL_STRIP]);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum StripKind {
+ Prefix,
+ Suffix,
+}
+
+impl<'tcx> LateLintPass<'tcx> for ManualStrip {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) {
+ return;
+ }
+
+ if_chain! {
+ if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
+ if let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id);
+ if let ExprKind::Path(target_path) = &target_arg.kind;
+ then {
+ let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
+ StripKind::Prefix
+ } else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
+ StripKind::Suffix
+ } else {
+ return;
+ };
+ let target_res = cx.qpath_res(target_path, target_arg.hir_id);
+ if target_res == Res::Err {
+ return;
+ };
+
+ if_chain! {
+ if let Res::Local(hir_id) = target_res;
+ if let Some(used_mutably) = mutated_variables(then, cx);
+ if used_mutably.contains(&hir_id);
+ then {
+ return;
+ }
+ }
+
+ let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
+ if !strippings.is_empty() {
+
+ let kind_word = match strip_kind {
+ StripKind::Prefix => "prefix",
+ StripKind::Suffix => "suffix",
+ };
+
+ let test_span = expr.span.until(then.span);
- &format!("try using the `strip_{}` method", kind_word),
++ span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {kind_word} manually"), |diag| {
++ diag.span_note(test_span, &format!("the {kind_word} was tested here"));
+ multispan_sugg(
+ diag,
- format!("if let Some(<stripped>) = {}.strip_{}({}) ",
++ &format!("try using the `strip_{kind_word}` method"),
+ vec![(test_span,
- kind_word,
++ format!("if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
+ snippet(cx, target_arg.span, ".."),
+ snippet(cx, pattern.span, "..")))]
+ .into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
+ );
+ });
+ }
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
+fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::MethodCall(_, arg, [], _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if match_def_path(cx, method_def_id, &paths::STR_LEN);
+ then {
+ Some(arg)
+ } else {
+ None
+ }
+ }
+}
+
+// Returns the length of the `expr` if it's a constant string or char.
+fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
+ let (value, _) = constant(cx, cx.typeck_results(), expr)?;
+ match value {
+ Constant::Str(value) => Some(value.len() as u128),
+ Constant::Char(value) => Some(value.len_utf8() as u128),
+ _ => None,
+ }
+}
+
+// Tests if `expr` equals the length of the pattern.
+fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Int(n, _),
+ ..
+ }) = expr.kind
+ {
+ constant_length(cx, pattern).map_or(false, |length| length == n)
+ } else {
+ len_arg(cx, expr).map_or(false, |arg| eq_expr_value(cx, pattern, arg))
+ }
+}
+
+// Tests if `expr` is a `&str`.
+fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match cx.typeck_results().expr_ty_adjusted(expr).kind() {
+ ty::Ref(_, ty, _) => ty.is_str(),
+ _ => false,
+ }
+}
+
+// Removes the outer `AddrOf` expression if needed.
+fn peel_ref<'a>(expr: &'a Expr<'_>) -> &'a Expr<'a> {
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, unref) = &expr.kind {
+ unref
+ } else {
+ expr
+ }
+}
+
+// Find expressions where `target` is stripped using the length of `pattern`.
+// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}`
+// method.
+fn find_stripping<'tcx>(
+ cx: &LateContext<'tcx>,
+ strip_kind: StripKind,
+ target: Res,
+ pattern: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) -> Vec<Span> {
+ struct StrippingFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ strip_kind: StripKind,
+ target: Res,
+ pattern: &'tcx Expr<'tcx>,
+ results: Vec<Span>,
+ }
+
+ impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ if_chain! {
+ if is_ref_str(self.cx, ex);
+ let unref = peel_ref(ex);
+ if let ExprKind::Index(indexed, index) = &unref.kind;
+ if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index);
+ if let ExprKind::Path(path) = &indexed.kind;
+ if self.cx.qpath_res(path, ex.hir_id) == self.target;
+ then {
+ match (self.strip_kind, start, end) {
+ (StripKind::Prefix, Some(start), None) => {
+ if eq_pattern_length(self.cx, self.pattern, start) {
+ self.results.push(ex.span);
+ return;
+ }
+ },
+ (StripKind::Suffix, None, Some(end)) => {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind;
+ if let Some(left_arg) = len_arg(self.cx, left);
+ if let ExprKind::Path(left_path) = &left_arg.kind;
+ if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target;
+ if eq_pattern_length(self.cx, self.pattern, right);
+ then {
+ self.results.push(ex.span);
+ return;
+ }
+ }
+ },
+ _ => {}
+ }
+ }
+ }
+
+ walk_expr(self, ex);
+ }
+ }
+
+ let mut finder = StrippingFinder {
+ cx,
+ strip_kind,
+ target,
+ pattern,
+ results: vec![],
+ };
+ walk_expr(&mut finder, expr);
+ finder.results
+}
--- /dev/null
- (&[], Some(inner_expr)) => {
+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<'a>(cx: &LateContext<'_>, expr: &'a 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()) {
- (&[ref inner_stmt], None) => {
++ ([], Some(inner_expr)) => {
+ // If block only contains an expression,
+ // reduce `{ X }` to `X`
+ reduce_unit_expression(cx, inner_expr)
+ },
- format!(
- "called `map(f)` on an `{0}` value where `f` is a {1} that returns the unit type `()`",
- map_type, function_type
- )
++ ([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
- "{} let Ok({}) = {}",
- ifwhile,
- some_expr_string,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::method_chain_args;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, PatKind, QPath};
+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 unnecessary `ok()` in `while let`.
+ ///
+ /// ### Why is this bad?
+ /// Calling `ok()` in `while let` is unnecessary, instead match
+ /// on `Ok(pat)`
+ ///
+ /// ### Example
+ /// ```ignore
+ /// while let Some(value) = iter.next().ok() {
+ /// vec.push(value)
+ /// }
+ ///
+ /// if let Some(value) = iter.next().ok() {
+ /// vec.push(value)
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// while let Ok(value) = iter.next() {
+ /// vec.push(value)
+ /// }
+ ///
+ /// if let Ok(value) = iter.next() {
+ /// vec.push(value)
+ /// }
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub MATCH_RESULT_OK,
+ style,
+ "usage of `ok()` in `let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead"
+}
+
+declare_lint_pass!(MatchResultOk => [MATCH_RESULT_OK]);
+
+impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (let_pat, let_expr, ifwhile) =
+ if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
+ (let_pat, let_expr, "if")
+ } else if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
+ (let_pat, let_expr, "while")
+ } else {
+ return;
+ };
+
+ if_chain! {
+ if let ExprKind::MethodCall(ok_path, result_types_0, ..) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
+ if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation
+ if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() method use std::marker::Sized;
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::Result);
+ if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";
+
+ then {
+
+ let mut applicability = Applicability::MachineApplicable;
+ let some_expr_string = snippet_with_applicability(cx, y[0].span, "", &mut applicability);
+ let trimmed_ok = snippet_with_applicability(cx, let_expr.span.until(ok_path.ident.span), "", &mut applicability);
+ let sugg = format!(
- &format!("consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string),
++ "{ifwhile} let Ok({some_expr_string}) = {}",
+ trimmed_ok.trim().trim_end_matches('.'),
+ );
+ span_lint_and_sugg(
+ cx,
+ MATCH_RESULT_OK,
+ expr.span.with_hi(let_expr.span.hi()),
+ "matching on `Some` with `ok()` is redundant",
++ &format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::IfLetOrMatch;
+use clippy_utils::visitors::is_local_used;
- PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
++use clippy_utils::{
++ is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq,
++};
+use if_chain::if_chain;
+use rustc_errors::MultiSpan;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+
+use super::COLLAPSIBLE_MATCH;
+
+pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
+ if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
+ for arm in arms {
+ check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
+ }
+ }
+}
+
+pub(super) fn check_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ body: &'tcx Expr<'_>,
+ else_expr: Option<&'tcx Expr<'_>>,
+) {
+ check_arm(cx, false, pat, body, None, else_expr);
+}
+
+fn check_arm<'tcx>(
+ cx: &LateContext<'tcx>,
+ outer_is_match: bool,
+ outer_pat: &'tcx Pat<'tcx>,
+ outer_then_body: &'tcx Expr<'tcx>,
+ outer_guard: Option<&'tcx Guard<'tcx>>,
+ outer_else_body: Option<&'tcx Expr<'tcx>>,
+) {
+ let inner_expr = peel_blocks_with_stmt(outer_then_body);
+ if_chain! {
+ if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr);
+ if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
+ IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)),
+ IfLetOrMatch::Match(scrutinee, arms, ..) => if_chain! {
+ // if there are more than two arms, collapsing would be non-trivial
+ if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none());
+ // one of the arms must be "wild-like"
+ if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a));
+ then {
+ let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
+ Some((scrutinee, then.pat, Some(els.body)))
+ } else {
+ None
+ }
+ },
+ };
+ if outer_pat.span.ctxt() == inner_scrutinee.span.ctxt();
+ // match expression must be a local binding
+ // match <local> { .. }
+ if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));
+ if !pat_contains_or(inner_then_pat);
+ // the binding must come from the pattern of the containing match arm
+ // ..<local>.. => match <local> { .. }
+ if let Some(binding_span) = find_pat_binding(outer_pat, binding_id);
+ // the "else" branches must be equal
+ if match (outer_else_body, inner_else_body) {
+ (None, None) => true,
+ (None, Some(e)) | (Some(e), None) => is_unit_expr(e),
+ (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
+ };
+ // the binding must not be used in the if guard
+ if outer_guard.map_or(
+ true,
+ |(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
+ );
+ // ...or anywhere in the inner expression
+ if match inner {
+ IfLetOrMatch::IfLet(_, _, body, els) => {
+ !is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
+ },
+ IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
+ };
+ then {
+ let msg = format!(
+ "this `{}` can be collapsed into the outer `{}`",
+ if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
+ if outer_is_match { "match" } else { "if let" },
+ );
+ span_lint_and_then(
+ cx,
+ COLLAPSIBLE_MATCH,
+ inner_expr.span,
+ &msg,
+ |diag| {
+ let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
+ help_span.push_span_label(binding_span, "replace this binding");
+ help_span.push_span_label(inner_then_pat.span, "with this pattern");
+ diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
+ },
+ );
+ }
+ }
+}
+
+/// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed"
+/// into a single wild arm without any significant loss in semantics or readability.
+fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if arm.guard.is_some() {
+ return false;
+ }
+ match arm.pat.kind {
+ PatKind::Binding(..) | PatKind::Wild => true,
++ PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone),
+ _ => false,
+ }
+}
+
+fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
+ let mut span = None;
+ pat.walk_short(|p| match &p.kind {
+ // ignore OR patterns
+ PatKind::Or(_) => false,
+ PatKind::Binding(_bm, _, _ident, _) => {
+ let found = p.hir_id == hir_id;
+ if found {
+ span = Some(p.span);
+ }
+ !found
+ },
+ _ => true,
+ });
+ span
+}
+
+fn pat_contains_or(pat: &Pat<'_>) -> bool {
+ let mut result = false;
+ pat.walk(|p| {
+ let is_or = matches!(p.kind, PatKind::Or(_));
+ result |= is_or;
+ !is_or
+ });
+ result
+}
--- /dev/null
- can_move_expr_to_closure, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id, peel_blocks,
- peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
+use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
+use clippy_utils::{
- format!("({})", scrutinee_str)
++ can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
++ peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
+};
+use rustc_ast::util::parser::PREC_POSTFIX;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{
+ def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
+ QPath, UnsafeSource,
+};
+use rustc_lint::LateContext;
+use rustc_span::{sym, SyntaxContext};
+
+use super::MANUAL_MAP;
+
+pub(super) fn check_match<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ scrutinee: &'tcx Expr<'_>,
+ arms: &'tcx [Arm<'_>],
+) {
+ if let [arm1, arm2] = arms
+ && arm1.guard.is_none()
+ && arm2.guard.is_none()
+ {
+ check(cx, expr, scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.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);
+}
+
+#[expect(clippy::too_many_lines)]
+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<'_>,
+) {
+ let (scrutinee_ty, ty_ref_count, ty_mutability) =
+ peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
+ if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
+ && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
+ {
+ return;
+ }
+
+ let expr_ctxt = expr.span.ctxt();
+ let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
+ try_parse_pattern(cx, then_pat, expr_ctxt),
+ else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
+ ) {
+ (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
+ (else_body, pattern, ref_count, true)
+ },
+ (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
+ (else_body, pattern, ref_count, false)
+ },
+ (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
+ (then_body, pattern, ref_count, true)
+ },
+ (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
+ (then_body, pattern, ref_count, false)
+ },
+ _ => return,
+ };
+
+ // Top level or patterns aren't allowed in closures.
+ if matches!(some_pat.kind, PatKind::Or(_)) {
+ return;
+ }
+
+ let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) {
+ Some(expr) => expr,
+ None => return,
+ };
+
+ // These two lints will go back and forth with each other.
+ if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
+ && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
+ {
+ return;
+ }
+
+ // `map` won't perform any adjustments.
+ if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
+ return;
+ }
+
+ // Determine which binding mode to use.
+ let explicit_ref = some_pat.contains_explicit_ref_binding();
+ let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability));
+
+ let as_ref_str = match binding_ref {
+ Some(Mutability::Mut) => ".as_mut()",
+ Some(Mutability::Not) => ".as_ref()",
+ None => "",
+ };
+
+ match can_move_expr_to_closure(cx, some_expr.expr) {
+ Some(captures) => {
+ // Check if captures the closure will need conflict with borrows made in the scrutinee.
+ // TODO: check all the references made in the scrutinee expression. This will require interacting
+ // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
+ if let Some(binding_ref_mutability) = binding_ref {
+ let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
+ ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
+ _ => None,
+ });
+ if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
+ match captures.get(l) {
+ Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
+ Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
+ return;
+ },
+ Some(CaptureKind::Ref(Mutability::Not)) | None => (),
+ }
+ }
+ }
+ },
+ None => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+
+ // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
+ // it's being passed by value.
+ let scrutinee = peel_hir_expr_refs(scrutinee).0;
+ let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
+ let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
- format!("|{}{}| unsafe {{ {} }}", annotation, some_binding, expr_snip)
++ format!("({scrutinee_str})")
+ } else {
+ scrutinee_str.into()
+ };
+
+ let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
+ if_chain! {
+ if !some_expr.needs_unsafe_block;
+ if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
+ if func.span.ctxt() == some_expr.expr.span.ctxt();
+ then {
+ snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
+ } else {
+ if path_to_local_id(some_expr.expr, id)
+ && !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
+ && binding_ref.is_some()
+ {
+ return;
+ }
+
+ // `ref` and `ref mut` annotations were handled earlier.
+ let annotation = if matches!(annotation, BindingAnnotation::MUT) {
+ "mut "
+ } else {
+ ""
+ };
+ let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
+ if some_expr.needs_unsafe_block {
- format!("|{}{}| {}", annotation, some_binding, expr_snip)
++ format!("|{annotation}{some_binding}| unsafe {{ {expr_snip} }}")
+ } else {
- format!("|{}| unsafe {{ {} }}", pat_snip, expr_snip)
++ format!("|{annotation}{some_binding}| {expr_snip}")
+ }
+ }
+ }
+ } else if !is_wild_none && explicit_ref.is_none() {
+ // TODO: handle explicit reference annotations.
+ let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
+ let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
+ if some_expr.needs_unsafe_block {
- format!("|{}| {}", pat_snip, expr_snip)
++ format!("|{pat_snip}| unsafe {{ {expr_snip} }}")
+ } else {
- format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
++ format!("|{pat_snip}| {expr_snip}")
+ }
+ } else {
+ // Refutable bindings and mixed reference annotations can't be handled by `map`.
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_MAP,
+ expr.span,
+ "manual implementation of `Option::map`",
+ "try this",
+ if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
- format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
++ format!("{{ {scrutinee_str}{as_ref_str}.map({body_str}) }}")
+ } else {
- PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
++ format!("{scrutinee_str}{as_ref_str}.map({body_str})")
+ },
+ app,
+ );
+}
+
+// Checks whether the expression could be passed as a function, or whether a closure is needed.
+// Returns the function to be passed to `map` if it exists.
+fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ match expr.kind {
+ ExprKind::Call(func, [arg])
+ if path_to_local_id(arg, binding)
+ && cx.typeck_results().expr_adjustments(arg).is_empty()
+ && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
+ {
+ Some(func)
+ },
+ _ => None,
+ }
+}
+
+enum OptionPat<'a> {
+ Wild,
+ None,
+ Some {
+ // The pattern contained in the `Some` tuple.
+ pattern: &'a Pat<'a>,
+ // The number of references before the `Some` tuple.
+ // e.g. `&&Some(_)` has a ref count of 2.
+ ref_count: usize,
+ },
+}
+
+struct SomeExpr<'tcx> {
+ expr: &'tcx Expr<'tcx>,
+ needs_unsafe_block: bool,
+}
+
+// Try to parse into a recognized `Option` pattern.
+// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
+fn try_parse_pattern<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
+ fn f<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ ref_count: usize,
+ ctxt: SyntaxContext,
+ ) -> Option<OptionPat<'tcx>> {
+ match pat.kind {
+ PatKind::Wild => Some(OptionPat::Wild),
+ PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
- if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
++ PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone) => {
++ Some(OptionPat::None)
++ },
+ PatKind::TupleStruct(ref qpath, [pattern], _)
- ExprKind::Call(
- Expr {
- kind: ExprKind::Path(ref qpath),
- ..
- },
- [arg],
- ) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr {
- expr: arg,
- needs_unsafe_block,
- }),
++ if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionSome) && pat.span.ctxt() == ctxt =>
+ {
+ Some(OptionPat::Some { pattern, ref_count })
+ },
+ _ => None,
+ }
+ }
+ f(cx, pat, 0, ctxt)
+}
+
+// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
+fn get_some_expr<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ needs_unsafe_block: bool,
+ ctxt: SyntaxContext,
+) -> Option<SomeExpr<'tcx>> {
+ // TODO: Allow more complex expressions.
+ match expr.kind {
- matches!(peel_blocks(expr).kind, ExprKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
++ ExprKind::Call(callee, [arg])
++ if ctxt == expr.span.ctxt() && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) =>
++ {
++ Some(SomeExpr {
++ expr: arg,
++ needs_unsafe_block,
++ })
++ },
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(expr),
+ rules,
+ ..
+ },
+ _,
+ ) => get_some_expr(
+ cx,
+ expr,
+ needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
+ ctxt,
+ ),
+ _ => None,
+ }
+}
+
+// Checks for the `None` value.
+fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
++ is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
+}
--- /dev/null
- use clippy_utils::{is_lang_ctor, path_to_local_id, sugg};
+use clippy_utils::consts::constant_simple;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::usage::contains_return_break_continue_macro;
- use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
++use clippy_utils::{is_res_lang_ctor, path_to_local_id, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- &format!("this pattern reimplements `{}::unwrap_or`", ty_name),
++use rustc_hir::def::{DefKind, Res};
++use rustc_hir::LangItem::{OptionNone, ResultErr};
+use rustc_hir::{Arm, Expr, PatKind};
+use rustc_lint::LateContext;
++use rustc_middle::ty::DefIdTree;
+use rustc_span::sym;
+
+use super::MANUAL_UNWRAP_OR;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
+ let ty = cx.typeck_results().expr_ty(scrutinee);
+ if_chain! {
+ if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
+ Some("Option")
+ } else if is_type_diagnostic_item(cx, ty, sym::Result) {
+ Some("Result")
+ } else {
+ None
+ };
+ if let Some(or_arm) = applicable_or_arm(cx, arms);
+ if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
+ if let Some(indent) = indent_of(cx, expr.span);
+ if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
+ then {
+ let reindented_or_body =
+ reindent_multiline(or_body_snippet.into(), true, Some(indent));
+
+ let suggestion = if scrutinee.span.from_expansion() {
+ // we don't want parentheses around macro, e.g. `(some_macro!()).unwrap_or(0)`
+ sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..")
+ }
+ else {
+ sugg::Sugg::hir(cx, scrutinee, "..").maybe_par()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_UNWRAP_OR, expr.span,
- "{}.unwrap_or({})",
- suggestion,
- reindented_or_body,
++ &format!("this pattern reimplements `{ty_name}::unwrap_or`"),
+ "replace with",
+ format!(
- PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
++ "{suggestion}.unwrap_or({reindented_or_body})",
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
+ if_chain! {
+ if arms.len() == 2;
+ if arms.iter().all(|arm| arm.guard.is_none());
+ if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
+ match arm.pat.kind {
- matches!(pat.kind, PatKind::Wild) && is_lang_ctor(cx, qpath, ResultErr),
++ PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone),
+ PatKind::TupleStruct(ref qpath, [pat], _) =>
- if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
++ matches!(pat.kind, PatKind::Wild)
++ && is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr),
+ _ => false,
+ }
+ });
+ let unwrap_arm = &arms[1 - idx];
+ if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind;
++ if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, unwrap_arm.pat.hir_id);
++ if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
++ if cx.tcx.lang_items().option_some_variant() == Some(variant_id)
++ || cx.tcx.lang_items().result_ok_variant() == Some(variant_id);
+ if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
+ if path_to_local_id(unwrap_arm.body, binding_hir_id);
+ if cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty();
+ if !contains_return_break_continue_macro(or_arm.body);
+ then {
+ Some(or_arm)
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{is_lang_ctor, peel_blocks};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
- &format!("use `{}()` instead", suggestion),
++use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks};
+use rustc_errors::Applicability;
+use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+
+use super::MATCH_AS_REF;
+
+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() {
+ let arm_ref_mut = if is_none_arm(cx, &arms[0]) {
+ is_ref_some_arm(cx, &arms[1])
+ } else if is_none_arm(cx, &arms[1]) {
+ is_ref_some_arm(cx, &arms[0])
+ } else {
+ None
+ };
+ if let Some(rb) = arm_ref_mut {
+ let suggestion = match rb {
+ Mutability::Not => "as_ref",
+ Mutability::Mut => "as_mut",
+ };
+
+ let output_ty = cx.typeck_results().expr_ty(expr);
+ let input_ty = cx.typeck_results().expr_ty(ex);
+
+ let cast = if_chain! {
+ if let ty::Adt(_, substs) = input_ty.kind();
+ let input_ty = substs.type_at(0);
+ if let ty::Adt(_, substs) = output_ty.kind();
+ let output_ty = substs.type_at(0);
+ if let ty::Ref(_, output_ty, _) = *output_ty.kind();
+ if input_ty != output_ty;
+ then {
+ ".map(|x| x as _)"
+ } else {
+ ""
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MATCH_AS_REF,
+ expr.span,
- "{}.{}(){}",
++ &format!("use `{suggestion}()` instead"),
+ "try this",
+ format!(
- suggestion,
- cast,
++ "{}.{suggestion}(){cast}",
+ snippet_with_applicability(cx, ex.span, "_", &mut applicability),
- matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, LangItem::OptionNone))
+ ),
+ applicability,
+ );
+ }
+ }
+}
+
+// Checks if arm has the form `None => None`
+fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
- if is_lang_ctor(cx, qpath, LangItem::OptionSome);
++ matches!(
++ arm.pat.kind,
++ PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionNone)
++ )
+}
+
+// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
+fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<Mutability> {
+ if_chain! {
+ if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
- if let ExprKind::Path(ref some_path) = e.kind;
- if is_lang_ctor(cx, some_path, LangItem::OptionSome);
++ if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionSome);
+ if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., ident, _) = first_pat.kind;
+ if let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind;
++ if is_res_lang_ctor(cx, path_res(cx, e), LangItem::OptionSome);
+ if let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind;
+ if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
+ then {
+ return Some(mutabl)
+ }
+ }
+ None
+}
--- /dev/null
- format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_wild;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::span_contains_comment;
+use rustc_ast::{Attribute, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat};
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::ty;
+use rustc_span::source_map::Spanned;
+
+use super::MATCH_LIKE_MATCHES_MACRO;
+
+/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
+pub(crate) 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<'_>,
+) {
+ find_matches_sugg(
+ cx,
+ let_expr,
+ IntoIterator::into_iter([
+ (&[][..], Some(let_pat), then_expr, None),
+ (&[][..], None, else_expr, None),
+ ]),
+ expr,
+ true,
+ );
+}
+
+pub(super) fn check_match<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ scrutinee: &'tcx Expr<'_>,
+ arms: &'tcx [Arm<'tcx>],
+) -> bool {
+ find_matches_sugg(
+ cx,
+ scrutinee,
+ arms.iter().map(|arm| {
+ (
+ cx.tcx.hir().attrs(arm.hir_id),
+ Some(arm.pat),
+ arm.body,
+ arm.guard.as_ref(),
+ )
+ }),
+ e,
+ false,
+ )
+}
+
+/// Lint a `match` or `if let` for replacement by `matches!`
+fn find_matches_sugg<'a, 'b, I>(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ mut iter: I,
+ expr: &Expr<'_>,
+ is_if_let: bool,
+) -> bool
+where
+ 'b: 'a,
+ I: Clone
+ + DoubleEndedIterator
+ + ExactSizeIterator
+ + Iterator<
+ Item = (
+ &'a [Attribute],
+ Option<&'a Pat<'b>>,
+ &'a Expr<'b>,
+ Option<&'a Guard<'b>>,
+ ),
+ >,
+{
+ if_chain! {
+ if !span_contains_comment(cx.sess().source_map(), expr.span);
+ if iter.len() >= 2;
+ if cx.typeck_results().expr_ty(expr).is_bool();
+ if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
+ let iter_without_last = iter.clone();
+ if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
+ if let Some(b0) = find_bool_lit(&first_expr.kind);
+ if let Some(b1) = find_bool_lit(&last_expr.kind);
+ if b0 != b1;
+ if first_guard.is_none() || iter.len() == 0;
+ if first_attrs.is_empty();
+ if iter
+ .all(|arm| {
+ find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
+ });
+ then {
+ if let Some(last_pat) = last_pat_opt {
+ if !is_wild(last_pat) {
+ return false;
+ }
+ }
+
+ // The suggestion may be incorrect, because some arms can have `cfg` attributes
+ // evaluated into `false` and so such arms will be stripped before.
+ let mut applicability = Applicability::MaybeIncorrect;
+ let pat = {
+ use itertools::Itertools as _;
+ iter_without_last
+ .filter_map(|arm| {
+ let pat_span = arm.1?.span;
+ Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
+ })
+ .join(" | ")
+ };
+ let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
- "{}matches!({}, {})",
++ format!("{pat} if {}", snippet_with_applicability(cx, g.span, "..", &mut applicability))
+ } else {
+ pat
+ };
+
+ // strip potential borrows (#6503), but only if the type is a reference
+ let mut ex_new = ex;
+ if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
+ if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
+ ex_new = ex_inner;
+ }
+ };
+ span_lint_and_sugg(
+ cx,
+ MATCH_LIKE_MATCHES_MACRO,
+ expr.span,
+ &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
+ "try this",
+ format!(
- pat_and_guard,
++ "{}matches!({}, {pat_and_guard})",
+ if b0 { "" } else { "!" },
+ snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ true
+ } else {
+ false
+ }
+ }
+}
+
+/// Extract a `bool` or `{ bool }`
+fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> {
+ match ex {
+ ExprKind::Lit(Spanned {
+ node: LitKind::Bool(b), ..
+ }) => Some(*b),
+ ExprKind::Block(
+ rustc_hir::Block {
+ stmts: &[],
+ expr: Some(exp),
+ ..
+ },
+ _,
+ ) => {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Bool(b), ..
+ }) = exp.kind
+ {
+ Some(b)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
--- /dev/null
- format!("{} | {}", keep_pat_snip, move_pat_snip),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash};
+use core::cmp::Ordering;
+use core::iter;
+use core::slice;
+use rustc_arena::DroplessArena;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatKind, RangeEnd};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::Symbol;
+
+use super::MATCH_SAME_ARMS;
+
+#[expect(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
+ let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(arm.body);
+ h.finish()
+ };
+
+ let arena = DroplessArena::default();
+ let normalized_pats: Vec<_> = arms
+ .iter()
+ .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat))
+ .collect();
+
+ // The furthest forwards a pattern can move without semantic changes
+ let forwards_blocking_idxs: Vec<_> = normalized_pats
+ .iter()
+ .enumerate()
+ .map(|(i, pat)| {
+ normalized_pats[i + 1..]
+ .iter()
+ .enumerate()
+ .find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j))
+ .unwrap_or(normalized_pats.len())
+ })
+ .collect();
+
+ // The furthest backwards a pattern can move without semantic changes
+ let backwards_blocking_idxs: Vec<_> = normalized_pats
+ .iter()
+ .enumerate()
+ .map(|(i, pat)| {
+ normalized_pats[..i]
+ .iter()
+ .enumerate()
+ .rev()
+ .zip(forwards_blocking_idxs[..i].iter().copied().rev())
+ .skip_while(|&(_, forward_block)| forward_block > i)
+ .find_map(|((j, other), forward_block)| {
+ (forward_block == i || pat.has_overlapping_values(other)).then_some(j)
+ })
+ .unwrap_or(0)
+ })
+ .collect();
+
+ let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
+ let min_index = usize::min(lindex, rindex);
+ let max_index = usize::max(lindex, rindex);
+
+ let mut local_map: HirIdMap<HirId> = HirIdMap::default();
+ let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
+ if_chain! {
+ if let Some(a_id) = path_to_local(a);
+ if let Some(b_id) = path_to_local(b);
+ let entry = match local_map.entry(a_id) {
+ HirIdMapEntry::Vacant(entry) => entry,
+ // check if using the same bindings as before
+ HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
+ };
+ // the names technically don't have to match; this makes the lint more conservative
+ if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
+ if cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b);
+ if pat_contains_local(lhs.pat, a_id);
+ if pat_contains_local(rhs.pat, b_id);
+ then {
+ entry.insert(b_id);
+ true
+ } else {
+ false
+ }
+ }
+ };
+ // Arms with a guard are ignored, those can’t always be merged together
+ // If both arms overlap with an arm in between then these can't be merged either.
+ !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
+ && lhs.guard.is_none()
+ && rhs.guard.is_none()
+ && SpanlessEq::new(cx)
+ .expr_fallback(eq_fallback)
+ .eq_expr(lhs.body, rhs.body)
+ // these checks could be removed to allow unused bindings
+ && bindings_eq(lhs.pat, local_map.keys().copied().collect())
+ && bindings_eq(rhs.pat, local_map.values().copied().collect())
+ };
+
+ let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
+ for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
+ if matches!(arm2.pat.kind, PatKind::Wild) {
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ arm1.span,
+ "this match arm has an identical body to the `_` wildcard arm",
+ |diag| {
+ diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect)
+ .help("or try changing either arm body")
+ .span_note(arm2.span, "`_` wildcard arm here");
+ },
+ );
+ } else {
+ let back_block = backwards_blocking_idxs[j];
+ let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
+ (arm1, arm2)
+ } else {
+ (arm2, arm1)
+ };
+
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ keep_arm.span,
+ "this match arm has an identical body to another arm",
+ |diag| {
+ let move_pat_snip = snippet(cx, move_arm.pat.span, "<pat2>");
+ let keep_pat_snip = snippet(cx, keep_arm.pat.span, "<pat1>");
+
+ diag.span_suggestion(
+ keep_arm.pat.span,
+ "try merging the arm patterns",
++ format!("{keep_pat_snip} | {move_pat_snip}"),
+ Applicability::MaybeIncorrect,
+ )
+ .help("or try changing either arm body")
+ .span_note(move_arm.span, "other arm here");
+ },
+ );
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum NormalizedPat<'a> {
+ Wild,
+ Struct(Option<DefId>, &'a [(Symbol, Self)]),
+ Tuple(Option<DefId>, &'a [Self]),
+ Or(&'a [Self]),
+ Path(Option<DefId>),
+ LitStr(Symbol),
+ LitBytes(&'a [u8]),
+ LitInt(u128),
+ LitBool(bool),
+ Range(PatRange),
+ /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise
+ /// the first value contains everything before the `..` wildcard pattern, and the second value
+ /// contains everything afterwards. Note that either side, or both sides, may contain zero
+ /// patterns.
+ Slice(&'a [Self], Option<&'a [Self]>),
+}
+
+#[derive(Clone, Copy)]
+struct PatRange {
+ start: u128,
+ end: u128,
+ bounds: RangeEnd,
+}
+impl PatRange {
+ fn contains(&self, x: u128) -> bool {
+ x >= self.start
+ && match self.bounds {
+ RangeEnd::Included => x <= self.end,
+ RangeEnd::Excluded => x < self.end,
+ }
+ }
+
+ fn overlaps(&self, other: &Self) -> bool {
+ // Note: Empty ranges are impossible, so this is correct even though it would return true if an
+ // empty exclusive range were to reside within an inclusive range.
+ (match self.bounds {
+ RangeEnd::Included => self.end >= other.start,
+ RangeEnd::Excluded => self.end > other.start,
+ } && match other.bounds {
+ RangeEnd::Included => self.start <= other.end,
+ RangeEnd::Excluded => self.start < other.end,
+ })
+ }
+}
+
+/// Iterates over the pairs of fields with matching names.
+fn iter_matching_struct_fields<'a>(
+ left: &'a [(Symbol, NormalizedPat<'a>)],
+ right: &'a [(Symbol, NormalizedPat<'a>)],
+) -> impl Iterator<Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>)> + 'a {
+ struct Iter<'a>(
+ slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+ slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+ );
+ impl<'a> Iterator for Iter<'a> {
+ type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>);
+ fn next(&mut self) -> Option<Self::Item> {
+ // Note: all the fields in each slice are sorted by symbol value.
+ let mut left = self.0.next()?;
+ let mut right = self.1.next()?;
+ loop {
+ match left.0.cmp(&right.0) {
+ Ordering::Equal => return Some((&left.1, &right.1)),
+ Ordering::Less => left = self.0.next()?,
+ Ordering::Greater => right = self.1.next()?,
+ }
+ }
+ }
+ }
+ Iter(left.iter(), right.iter())
+}
+
+#[expect(clippy::similar_names)]
+impl<'a> NormalizedPat<'a> {
+ #[expect(clippy::too_many_lines)]
+ fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self {
+ match pat.kind {
+ PatKind::Wild | PatKind::Binding(.., None) => Self::Wild,
+ PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => {
+ Self::from_pat(cx, arena, pat)
+ },
+ PatKind::Struct(ref path, fields, _) => {
+ let fields =
+ arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat))));
+ fields.sort_by_key(|&(name, _)| name);
+ Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields)
+ },
+ PatKind::TupleStruct(ref path, pats, wild_idx) => {
+ let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() {
+ Some(x) => x,
+ None => return Self::Wild,
+ };
+ let (var_id, variant) = if adt.is_enum() {
+ match cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ Some(x) => (Some(x), adt.variant_with_ctor_id(x)),
+ None => return Self::Wild,
+ }
+ } else {
+ (None, adt.non_enum_variant())
+ };
+ let (front, back) = match wild_idx.as_opt_usize() {
+ Some(i) => pats.split_at(i),
+ None => (pats, [].as_slice()),
+ };
+ let pats = arena.alloc_from_iter(
+ front
+ .iter()
+ .map(|pat| Self::from_pat(cx, arena, pat))
+ .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len()))
+ .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ );
+ Self::Tuple(var_id, pats)
+ },
+ PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+ PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()),
+ PatKind::Tuple(pats, wild_idx) => {
+ let field_count = match cx.typeck_results().pat_ty(pat).kind() {
+ ty::Tuple(subs) => subs.len(),
+ _ => return Self::Wild,
+ };
+ let (front, back) = match wild_idx.as_opt_usize() {
+ Some(i) => pats.split_at(i),
+ None => (pats, [].as_slice()),
+ };
+ let pats = arena.alloc_from_iter(
+ front
+ .iter()
+ .map(|pat| Self::from_pat(cx, arena, pat))
+ .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len()))
+ .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ );
+ Self::Tuple(None, pats)
+ },
+ PatKind::Lit(e) => match &e.kind {
+ // TODO: Handle negative integers. They're currently treated as a wild match.
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Str(sym, _) => Self::LitStr(sym),
+ LitKind::ByteStr(ref bytes) => Self::LitBytes(bytes),
+ LitKind::Byte(val) => Self::LitInt(val.into()),
+ LitKind::Char(val) => Self::LitInt(val.into()),
+ LitKind::Int(val, _) => Self::LitInt(val),
+ LitKind::Bool(val) => Self::LitBool(val),
+ LitKind::Float(..) | LitKind::Err => Self::Wild,
+ },
+ _ => Self::Wild,
+ },
+ PatKind::Range(start, end, bounds) => {
+ // TODO: Handle negative integers. They're currently treated as a wild match.
+ let start = match start {
+ None => 0,
+ Some(e) => match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Int(val, _) => val,
+ LitKind::Char(val) => val.into(),
+ LitKind::Byte(val) => val.into(),
+ _ => return Self::Wild,
+ },
+ _ => return Self::Wild,
+ },
+ };
+ let (end, bounds) = match end {
+ None => (u128::MAX, RangeEnd::Included),
+ Some(e) => match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Int(val, _) => (val, bounds),
+ LitKind::Char(val) => (val.into(), bounds),
+ LitKind::Byte(val) => (val.into(), bounds),
+ _ => return Self::Wild,
+ },
+ _ => return Self::Wild,
+ },
+ };
+ Self::Range(PatRange { start, end, bounds })
+ },
+ PatKind::Slice(front, wild_pat, back) => Self::Slice(
+ arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+ ),
+ }
+ }
+
+ /// Checks if two patterns overlap in the values they can match assuming they are for the same
+ /// type.
+ fn has_overlapping_values(&self, other: &Self) -> bool {
+ match (*self, *other) {
+ (Self::Wild, _) | (_, Self::Wild) => true,
+ (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => {
+ pats.iter().any(|pat| pat.has_overlapping_values(other))
+ },
+ (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => {
+ if lpath != rpath {
+ return false;
+ }
+ iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+ },
+ (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => {
+ if lpath != rpath {
+ return false;
+ }
+ lpats
+ .iter()
+ .zip(rpats.iter())
+ .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+ },
+ (Self::Path(x), Self::Path(y)) => x == y,
+ (Self::LitStr(x), Self::LitStr(y)) => x == y,
+ (Self::LitBytes(x), Self::LitBytes(y)) => x == y,
+ (Self::LitInt(x), Self::LitInt(y)) => x == y,
+ (Self::LitBool(x), Self::LitBool(y)) => x == y,
+ (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y),
+ (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x),
+ (Self::Slice(lpats, None), Self::Slice(rpats, None)) => {
+ lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y))
+ },
+ (Self::Slice(pats, None), Self::Slice(front, Some(back)))
+ | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
+ // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
+ // then the minimum length required will be greater than the length of `pats`.
+ if pats.len() < front.len() + back.len() {
+ return false;
+ }
+ pats[..front.len()]
+ .iter()
+ .zip(front.iter())
+ .chain(pats[pats.len() - back.len()..].iter().zip(back.iter()))
+ .all(|(x, y)| x.has_overlapping_values(y))
+ },
+ (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront
+ .iter()
+ .zip(rfront.iter())
+ .chain(lback.iter().rev().zip(rback.iter().rev()))
+ .all(|(x, y)| x.has_overlapping_values(y)),
+
+ // Enums can mix unit variants with tuple/struct variants. These can never overlap.
+ (Self::Path(_), Self::Tuple(..) | Self::Struct(..))
+ | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false,
+
+ // Tuples can be matched like a struct.
+ (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => {
+ // TODO: check fields here.
+ x == y
+ },
+
+ // TODO: Lit* with Path, Range with Path, LitBytes with Slice
+ _ => true,
+ }
+ }
+}
+
+fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
+ let mut result = false;
+ pat.walk_short(|p| {
+ result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
+ !result
+ });
+ result
+}
+
+/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
+fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
+ let mut result = true;
+ pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
+ result && ids.is_empty()
+}
--- /dev/null
- "let {} = {};\n{}let {} = {};",
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::macros::HirNode;
+use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
+use rustc_errors::Applicability;
+use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+
+use super::MATCH_SINGLE_BINDING;
+
+enum AssignmentExpr {
+ Assign { span: Span, match_span: Span },
+ Local { span: Span, pat_span: Span },
+}
+
+#[expect(clippy::too_many_lines)]
+pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'a>) {
+ if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
+ return;
+ }
+
+ let matched_vars = ex.span;
+ let bind_names = arms[0].pat.span;
+ let match_body = peel_blocks(arms[0].body);
+ let mut snippet_body = if match_body.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
+ } else {
+ snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
+ };
+
+ // Do we need to add ';' to suggestion ?
+ match match_body.kind {
+ ExprKind::Block(block, _) => {
+ // macro + expr_ty(body) == ()
+ if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ _ => {
+ // expr_ty(body) == ()
+ if cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ }
+
+ let mut applicability = Applicability::MaybeIncorrect;
+ match arms[0].pat.kind {
+ PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
+ let (target_span, sugg) = match opt_parent_assign_span(cx, ex) {
+ Some(AssignmentExpr::Assign { span, match_span }) => {
+ let sugg = sugg_with_curlies(
+ cx,
+ (ex, expr),
+ (bind_names, matched_vars),
+ &snippet_body,
+ &mut applicability,
+ Some(span),
+ );
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ span.to(match_span),
+ "this assignment could be simplified",
+ "consider removing the `match` expression",
+ sugg,
+ applicability,
+ );
+
+ return;
+ },
+ Some(AssignmentExpr::Local { span, pat_span }) => (
+ span,
+ format!(
- snippet_with_applicability(cx, pat_span, "..", &mut applicability),
- snippet_body
++ "let {} = {};\n{}let {} = {snippet_body};",
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
- "{};\n{}{}",
- snippet_with_applicability(cx, ex.span, "..", &mut applicability),
- indent,
- snippet_body
++ snippet_with_applicability(cx, pat_span, "..", &mut applicability)
+ ),
+ ),
+ None => {
+ let sugg = sugg_with_curlies(
+ cx,
+ (ex, expr),
+ (bind_names, matched_vars),
+ &snippet_body,
+ &mut applicability,
+ None,
+ );
+ (expr.span, sugg)
+ },
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ target_span,
+ "this match could be written as a `let` statement",
+ "consider using a `let` statement",
+ sugg,
+ applicability,
+ );
+ },
+ PatKind::Wild => {
+ if ex.can_have_side_effects() {
+ let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
+ let sugg = format!(
- cbrace_end = format!("\n{}}}", indent);
++ "{};\n{indent}{snippet_body}",
++ snippet_with_applicability(cx, ex.span, "..", &mut applicability)
+ );
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its scrutinee and body",
+ "consider using the scrutinee and body instead",
+ sugg,
+ applicability,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its body itself",
+ "consider using the match body instead",
+ snippet_body,
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+}
+
+/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
+fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
+ let map = &cx.tcx.hir();
+
+ if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)) {
+ return match map.find(map.get_parent_node(parent_arm_expr.hir_id)) {
+ Some(Node::Local(parent_let_expr)) => Some(AssignmentExpr::Local {
+ span: parent_let_expr.span,
+ pat_span: parent_let_expr.pat.span(),
+ }),
+ Some(Node::Expr(Expr {
+ kind: ExprKind::Assign(parent_assign_expr, match_expr, _),
+ ..
+ })) => Some(AssignmentExpr::Assign {
+ span: parent_assign_expr.span,
+ match_span: match_expr.span,
+ }),
+ _ => None,
+ };
+ }
+
+ None
+}
+
+fn sugg_with_curlies<'a>(
+ cx: &LateContext<'a>,
+ (ex, match_expr): (&Expr<'a>, &Expr<'a>),
+ (bind_names, matched_vars): (Span, Span),
+ snippet_body: &str,
+ applicability: &mut Applicability,
+ assignment: Option<Span>,
+) -> String {
+ let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
+
+ let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
+ if let Some(parent_expr) = get_parent_expr(cx, match_expr) {
+ if let ExprKind::Closure { .. } = parent_expr.kind {
- cbrace_start = format!("{{\n{}", indent);
++ cbrace_end = format!("\n{indent}}}");
+ // Fix body indent due to the closure
+ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
- cbrace_end = format!("\n{}}}", indent);
++ cbrace_start = format!("{{\n{indent}");
+ }
+ }
+
+ // If the parent is already an arm, and the body is another match statement,
+ // we need curly braces around suggestion
+ let parent_node_id = cx.tcx.hir().get_parent_node(match_expr.hir_id);
+ if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
+ if let ExprKind::Match(..) = arm.body.kind {
- cbrace_start = format!("{{\n{}", indent);
++ cbrace_end = format!("\n{indent}}}");
+ // Fix body indent due to the match
+ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
- "{}let {} = {};\n{}{}{}{}",
- cbrace_start,
++ cbrace_start = format!("{{\n{indent}");
+ }
+ }
+
+ let assignment_str = assignment.map_or_else(String::new, |span| {
+ let mut s = snippet(cx, span, "..").to_string();
+ s.push_str(" = ");
+ s
+ });
+
+ format!(
- snippet_with_applicability(cx, matched_vars, "..", applicability),
- indent,
- assignment_str,
- snippet_body,
- cbrace_end
++ "{cbrace_start}let {} = {};\n{indent}{assignment_str}{snippet_body}{cbrace_end}",
+ snippet_with_applicability(cx, bind_names, "..", applicability),
++ snippet_with_applicability(cx, matched_vars, "..", applicability)
+ )
+}
--- /dev/null
- &format!("consider changing the case of this arm to respect `{}`", method_str),
- format!("\"{}\"", suggestion),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{Arm, Expr, ExprKind, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::Symbol;
+use rustc_span::{sym, Span};
+
+use super::MATCH_STR_CASE_MISMATCH;
+
+#[derive(Debug)]
+enum CaseMethod {
+ LowerCase,
+ AsciiLowerCase,
+ UpperCase,
+ AsciiUppercase,
+}
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
+ if_chain! {
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind();
+ if let ty::Str = ty.kind();
+ then {
+ let mut visitor = MatchExprVisitor {
+ cx,
+ case_method: None,
+ };
+
+ visitor.visit_expr(scrutinee);
+
+ if let Some(case_method) = visitor.case_method {
+ if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) {
+ lint(cx, &case_method, bad_case_span, bad_case_sym.as_str());
+ }
+ }
+ }
+ }
+}
+
+struct MatchExprVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ case_method: Option<CaseMethod>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MatchExprVisitor<'a, 'tcx> {
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ match ex.kind {
+ ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {},
+ _ => walk_expr(self, ex),
+ }
+ }
+}
+
+impl<'a, 'tcx> MatchExprVisitor<'a, 'tcx> {
+ fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool {
+ if let Some(case_method) = get_case_method(segment_ident) {
+ let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();
+
+ if is_type_diagnostic_item(self.cx, ty, sym::String) || ty.kind() == &ty::Str {
+ self.case_method = Some(case_method);
+ return true;
+ }
+ }
+
+ false
+ }
+}
+
+fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> {
+ match segment_ident_str {
+ "to_lowercase" => Some(CaseMethod::LowerCase),
+ "to_ascii_lowercase" => Some(CaseMethod::AsciiLowerCase),
+ "to_uppercase" => Some(CaseMethod::UpperCase),
+ "to_ascii_uppercase" => Some(CaseMethod::AsciiUppercase),
+ _ => None,
+ }
+}
+
+fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(Span, Symbol)> {
+ let case_check = match case_method {
+ CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(|c| c.to_lowercase().next() == Some(c)) },
+ CaseMethod::AsciiLowerCase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_uppercase()) },
+ CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(|c| c.to_uppercase().next() == Some(c)) },
+ CaseMethod::AsciiUppercase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_lowercase()) },
+ };
+
+ for arm in arms {
+ if_chain! {
+ if let PatKind::Lit(Expr {
+ kind: ExprKind::Lit(lit),
+ ..
+ }) = arm.pat.kind;
+ if let LitKind::Str(symbol, _) = lit.node;
+ let input = symbol.as_str();
+ if !case_check(input);
+ then {
+ return Some((lit.span, symbol));
+ }
+ }
+ }
+
+ None
+}
+
+fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad_case_str: &str) {
+ let (method_str, suggestion) = match case_method {
+ CaseMethod::LowerCase => ("to_lowercase", bad_case_str.to_lowercase()),
+ CaseMethod::AsciiLowerCase => ("to_ascii_lowercase", bad_case_str.to_ascii_lowercase()),
+ CaseMethod::UpperCase => ("to_uppercase", bad_case_str.to_uppercase()),
+ CaseMethod::AsciiUppercase => ("to_ascii_uppercase", bad_case_str.to_ascii_uppercase()),
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_STR_CASE_MISMATCH,
+ bad_case_span,
+ "this `match` arm has a differing case than its expression",
++ &format!("consider changing the case of this arm to respect `{method_str}`"),
++ format!("\"{suggestion}\""),
+ Applicability::MachineApplicable,
+ );
+}
--- /dev/null
- &format!("`Err({})` matches all errors", ident_bind_name),
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::macros::{is_panic, root_macro_call};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{is_wild, peel_blocks_with_stmt};
+use rustc_hir::{Arm, Expr, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::symbol::{kw, sym};
+
+use super::MATCH_WILD_ERR_ARM;
+
+pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) {
+ let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs();
+ if is_type_diagnostic_item(cx, ex_ty, sym::Result) {
+ for arm in arms {
+ if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind {
+ let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false));
+ if path_str == "Err" {
+ let mut matching_wild = inner.iter().any(is_wild);
+ let mut ident_bind_name = kw::Underscore;
+ if !matching_wild {
+ // Looking for unused bindings (i.e.: `_e`)
+ for pat in inner.iter() {
+ if let PatKind::Binding(_, id, ident, None) = pat.kind {
+ if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
+ ident_bind_name = ident.name;
+ matching_wild = true;
+ }
+ }
+ }
+ }
+ if_chain! {
+ if matching_wild;
+ if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span);
+ if is_panic(cx, macro_call.def_id);
+ then {
+ // `Err(_)` or `Err(_e)` arm with `panic!` found
+ span_lint_and_note(cx,
+ MATCH_WILD_ERR_ARM,
+ arm.pat.span,
++ &format!("`Err({ident_bind_name})` matches all errors"),
+ None,
+ "match each error separately or use the error output, or use `.expect(msg)` if the error case is unreachable",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor, over,
+use super::NEEDLESS_MATCH;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
+use clippy_utils::{
- use rustc_hir_analysis::hir_ty_to_ty;
++ eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_res_lang_ctor, over, path_res,
+ peel_blocks_with_stmt,
+};
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::LateContext;
+use rustc_span::sym;
- if let ExprKind::Path(ref qpath) = ret.kind {
- return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
- }
- return false;
+
+pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_MATCH,
+ expr.span,
+ "this match expression is unnecessary",
+ "replace it with",
+ snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
+ applicability,
+ );
+ }
+}
+
+/// Check for nop `if let` expression that assembled as unnecessary match
+///
+/// ```rust,ignore
+/// if let Some(a) = option {
+/// Some(a)
+/// } else {
+/// None
+/// }
+/// ```
+/// OR
+/// ```rust,ignore
+/// if let SomeEnum::A = some_enum {
+/// SomeEnum::A
+/// } else if let SomeEnum::B = some_enum {
+/// SomeEnum::B
+/// } else {
+/// some_enum
+/// }
+/// ```
+pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: &higher::IfLet<'tcx>) {
+ if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let_inner(cx, if_let) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_MATCH,
+ ex.span,
+ "this if-let expression is unnecessary",
+ "replace it with",
+ snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
+ applicability,
+ );
+ }
+}
+
+fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
+ for arm in arms {
+ let arm_expr = peel_blocks_with_stmt(arm.body);
+
+ if let Some(guard_expr) = &arm.guard {
+ match guard_expr {
+ // gives up if `pat if expr` can have side effects
+ Guard::If(if_cond) => {
+ if if_cond.can_have_side_effects() {
+ return false;
+ }
+ },
+ // gives up `pat if let ...` arm
+ Guard::IfLet(_) => {
+ return false;
+ },
+ };
+ }
+
+ if let PatKind::Wild = arm.pat.kind {
+ if !eq_expr_value(cx, match_expr, strip_return(arm_expr)) {
+ return false;
+ }
+ } else if !pat_same_as_expr(arm.pat, arm_expr) {
+ return false;
+ }
+ }
+
+ true
+}
+
+fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
+ if let Some(if_else) = if_let.if_else {
+ if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
+ return false;
+ }
+
+ // Recursively check for each `else if let` phrase,
+ if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
+ return check_if_let_inner(cx, nested_if_let);
+ }
+
+ if matches!(if_else.kind, ExprKind::Block(..)) {
+ let else_expr = peel_blocks_with_stmt(if_else);
+ if matches!(else_expr.kind, ExprKind::Block(..)) {
+ return false;
+ }
+ let ret = strip_return(else_expr);
+ let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
+ if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
++ return is_res_lang_ctor(cx, path_res(cx, ret), OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
+ }
+ return eq_expr_value(cx, if_let.let_expr, ret);
+ }
+ }
+
+ false
+}
+
+/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
+fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+ if let ExprKind::Ret(Some(ret)) = expr.kind {
+ ret
+ } else {
+ expr
+ }
+}
+
+/// Manually check for coercion casting by checking if the type of the match operand or let expr
+/// differs with the assigned local variable or the function return type.
+fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
+ if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
+ match p_node {
+ // Compare match_expr ty with local in `let local = match match_expr {..}`
+ Node::Local(local) => {
+ let results = cx.typeck_results();
+ return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
+ },
+ // compare match_expr ty with RetTy in `fn foo() -> RetTy`
+ Node::Item(..) => {
+ if let Some(fn_decl) = p_node.fn_decl() {
+ if let FnRetTy::Return(ret_ty) = fn_decl.output {
+ return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
+ }
+ }
+ },
+ // check the parent expr for this whole block `{ match match_expr {..} }`
+ Node::Block(block) => {
+ if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
+ return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
+ }
+ },
+ // recursively call on `if xxx {..}` etc.
+ Node::Expr(p_expr) => {
+ return expr_ty_matches_p_ty(cx, expr, p_expr);
+ },
+ _ => {},
+ }
+ }
+ false
+}
+
+fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
+ let expr = strip_return(expr);
+ match (&pat.kind, &expr.kind) {
+ // Example: `Some(val) => Some(val)`
+ (PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
+ if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
+ return over(path.segments, call_path.segments, |pat_seg, call_seg| {
+ pat_seg.ident.name == call_seg.ident.name
+ }) && same_non_ref_symbols(tuple_params, call_params);
+ }
+ },
+ // Example: `val => val`
+ (
+ PatKind::Binding(annot, _, pat_ident, _),
+ ExprKind::Path(QPath::Resolved(
+ _,
+ Path {
+ segments: [first_seg, ..],
+ ..
+ },
+ )),
+ ) => {
+ return !matches!(annot, BindingAnnotation(ByRef::Yes, _)) && pat_ident.name == first_seg.ident.name;
+ },
+ // Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
+ (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
+ return over(p_path.segments, e_path.segments, |p_seg, e_seg| {
+ p_seg.ident.name == e_seg.ident.name
+ });
+ },
+ // Example: `5 => 5`
+ (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
+ if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
+ return pat_spanned.node == expr_spanned.node;
+ }
+ },
+ _ => {},
+ }
+
+ false
+}
+
+fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
+ if pats.len() != exprs.len() {
+ return false;
+ }
+
+ for i in 0..pats.len() {
+ if !pat_same_as_expr(&pats[i], &exprs[i]) {
+ return false;
+ }
+ }
+
+ true
+}
--- /dev/null
- use clippy_utils::{higher, is_lang_ctor, is_trait_method};
+use super::REDUNDANT_PATTERN_MATCHING;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop};
+use clippy_utils::visitors::any_temporaries_need_ordered_drop;
- use rustc_hir::LangItem::{self, OptionSome, OptionNone, PollPending, PollReady, ResultOk, ResultErr};
++use clippy_utils::{higher, is_trait_method};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
- let method = if is_lang_ctor(cx, path, OptionNone) {
- "is_none()"
- } else if is_lang_ctor(cx, path, PollPending) {
- "is_pending()"
++use rustc_hir::def::{DefKind, Res};
++use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
+use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
+use rustc_span::{sym, Symbol};
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
+ find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
+ }
+}
+
+pub(super) fn check_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ pat: &'tcx Pat<'_>,
+ scrutinee: &'tcx Expr<'_>,
+ has_else: bool,
+) {
+ find_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else);
+}
+
+// Extract the generic arguments out of a type
+fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
+ if_chain! {
+ if let ty::Adt(_, subs) = ty.kind();
+ if let Some(sub) = subs.get(index);
+ if let GenericArgKind::Type(sub_ty) = sub.unpack();
+ then {
+ Some(sub_ty)
+ } else {
+ None
+ }
+ }
+}
+
+fn find_sugg_for_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ let_pat: &Pat<'_>,
+ let_expr: &'tcx Expr<'_>,
+ keyword: &'static str,
+ has_else: bool,
+) {
+ // also look inside refs
+ // if we have &None for example, peel it so we can detect "if let None = x"
+ let check_pat = match let_pat.kind {
+ PatKind::Ref(inner, _mutability) => inner,
+ _ => let_pat,
+ };
+ let op_ty = cx.typeck_results().expr_ty(let_expr);
+ // Determine which function should be used, and the type contained by the corresponding
+ // variant.
+ let (good_method, inner_ty) = match check_pat.kind {
+ PatKind::TupleStruct(ref qpath, [sub_pat], _) => {
+ if let PatKind::Wild = sub_pat.kind {
+ let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id);
+ let Some(id) = res.opt_def_id().map(|ctor_id| cx.tcx.parent(ctor_id)) else { return };
+ let lang_items = cx.tcx.lang_items();
+ if Some(id) == lang_items.result_ok_variant() {
+ ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
+ } else if Some(id) == lang_items.result_err_variant() {
+ ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
+ } else if Some(id) == lang_items.option_some_variant() {
+ ("is_some()", op_ty)
+ } else if Some(id) == lang_items.poll_ready_variant() {
+ ("is_ready()", op_ty)
+ } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V4))) {
+ ("is_ipv4()", op_ty)
+ } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V6))) {
+ ("is_ipv6()", op_ty)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ },
+ PatKind::Path(ref path) => {
- };
- // `None` and `Pending` don't have an inner type.
- (method, cx.tcx.types.unit)
++ if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(path, check_pat.hir_id)
++ && let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
++ {
++ let method = if cx.tcx.lang_items().option_none_variant() == Some(variant_id) {
++ "is_none()"
++ } else if cx.tcx.lang_items().poll_pending_variant() == Some(variant_id) {
++ "is_pending()"
++ } else {
++ return;
++ };
++ // `None` and `Pending` don't have an inner type.
++ (method, cx.tcx.types.unit)
+ } else {
+ return;
- &format!("redundant pattern matching, consider using `{}`", good_method),
++ }
+ },
+ _ => return,
+ };
+
+ // If this is the last expression in a block or there is an else clause then the whole
+ // type needs to be considered, not just the inner type of the branch being matched on.
+ // Note the last expression in a block is dropped after all local bindings.
+ let check_ty = if has_else
+ || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
+ {
+ op_ty
+ } else {
+ inner_ty
+ };
+
+ // All temporaries created in the scrutinee expression are dropped at the same time as the
+ // scrutinee would be, so they have to be considered as well.
+ // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
+ // for the duration if body.
+ let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr);
+
+ // check that `while_let_on_iterator` lint does not trigger
+ if_chain! {
+ if keyword == "while";
+ if let ExprKind::MethodCall(method_path, ..) = let_expr.kind;
+ if method_path.ident.name == sym::next;
+ if is_trait_method(cx, let_expr, sym::Iterator);
+ then {
+ return;
+ }
+ }
+
+ let result_expr = match &let_expr.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ ExprKind::Unary(UnOp::Deref, deref) => deref,
+ _ => let_expr,
+ };
+
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ let_pat.span,
- diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
++ &format!("redundant pattern matching, consider using `{good_method}`"),
+ |diag| {
+ // if/while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ let expr_span = expr.span;
+
+ // if/while let ... = ... { ... }
+ // ^^^
+ let op_span = result_expr.span.source_callsite();
+
+ // if/while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^
+ let span = expr_span.until(op_span.shrink_to_hi());
+
+ let app = if needs_drop {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
+
+ let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
+ .maybe_par()
+ .to_string();
+
-
++ diag.span_suggestion(span, "try this", format!("{keyword} {sugg}.{good_method}"), app);
+
+ if needs_drop {
+ diag.note("this will change drop order of the result, as well as all temporaries");
+ diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
+ }
+ },
+ );
+}
+
+pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
+ if arms.len() == 2 {
+ let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
+
+ let found_good_method = match node_pair {
+ (
+ PatKind::TupleStruct(ref path_left, patterns_left, _),
+ PatKind::TupleStruct(ref path_right, patterns_right, _),
+ ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
+ if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ Item::Lang(ResultOk),
+ Item::Lang(ResultErr),
+ "is_ok()",
+ "is_err()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ Item::Diag(sym::IpAddr, sym!(V4)),
+ Item::Diag(sym::IpAddr, sym!(V6)),
+ "is_ipv4()",
+ "is_ipv6()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
+ | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
+ if patterns.len() == 1 =>
+ {
+ if let PatKind::Wild = patterns[0].kind {
- &format!("redundant pattern matching, consider using `{}`", good_method),
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ Item::Lang(OptionSome),
+ Item::Lang(OptionNone),
+ "is_some()",
+ "is_none()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ Item::Lang(PollReady),
+ Item::Lang(PollPending),
+ "is_ready()",
+ "is_pending()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+
+ if let Some(good_method) = found_good_method {
+ let span = expr.span.to(op.span);
+ let result_expr = match &op.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ _ => op,
+ };
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ expr.span,
- format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
++ &format!("redundant pattern matching, consider using `{good_method}`"),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try this",
- Lang(LangItem),
- Diag(Symbol, Symbol),
++ format!("{}.{good_method}", snippet(cx, result_expr.span, "_")),
+ Applicability::MaybeIncorrect, // snippet
+ );
+ },
+ );
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Item {
- let variant = ty.ty_adt_def()
++ Lang(LangItem),
++ Diag(Symbol, Symbol),
+}
+
+fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expected_item: Item) -> bool {
+ let Some(id) = cx.typeck_results().qpath_res(path, pat.hir_id).opt_def_id() else { return false };
+
+ match expected_item {
+ Item::Lang(expected_lang_item) => {
+ let expected_id = cx.tcx.lang_items().require(expected_lang_item).unwrap();
+ cx.tcx.parent(id) == expected_id
+ },
+ Item::Diag(expected_ty, expected_variant) => {
+ let ty = cx.typeck_results().pat_ty(pat);
+
+ if is_type_diagnostic_item(cx, ty, expected_ty) {
- return variant.name == expected_variant
++ let variant = ty
++ .ty_adt_def()
+ .expect("struct pattern type is not an ADT")
+ .variant_of_res(cx.qpath_res(path, pat.hir_id));
+
- }
++ return variant.name == expected_variant;
+ }
+
+ false
- let pat_left = arms[0].pat;
- let pat_right = arms[1].pat;
++ },
+ }
+}
+
+#[expect(clippy::too_many_arguments)]
+fn find_good_method_for_match<'a>(
+ cx: &LateContext<'_>,
+ arms: &[Arm<'_>],
+ path_left: &QPath<'_>,
+ path_right: &QPath<'_>,
+ expected_item_left: Item,
+ expected_item_right: Item,
+ should_be_left: &'a str,
+ should_be_right: &'a str,
+) -> Option<&'a str> {
- let body_node_pair = if (
- is_pat_variant(cx, pat_left, path_left, expected_item_left)
- ) && (
- is_pat_variant(cx, pat_right, path_right, expected_item_right)
- ) {
++ let first_pat = arms[0].pat;
++ let second_pat = arms[1].pat;
+
- } else if (
- is_pat_variant(cx, pat_left, path_left, expected_item_right)
- ) && (
- is_pat_variant(cx, pat_right, path_right, expected_item_left)
- ) {
++ let body_node_pair = if (is_pat_variant(cx, first_pat, path_left, expected_item_left))
++ && (is_pat_variant(cx, second_pat, path_right, expected_item_right))
++ {
+ (&arms[0].body.kind, &arms[1].body.kind)
++ } else if (is_pat_variant(cx, first_pat, path_left, expected_item_right))
++ && (is_pat_variant(cx, second_pat, path_right, expected_item_left))
++ {
+ (&arms[1].body.kind, &arms[0].body.kind)
+ } else {
+ return None;
+ };
+
+ match body_node_pair {
+ (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
+ (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
+ (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
+ _ => None,
+ },
+ _ => None,
+ }
+}
--- /dev/null
- format!("let value = *{};\n{}", original, trailing_indent)
+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!("{};\n{}", original, trailing_indent)
++ 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!("let value = {};\n{}", original, trailing_indent)
++ 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.
+fn has_significant_drop_in_scrutinee<'tcx, 'a>(
+ cx: &'a 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.
+ /// However, if we are at the 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, 'a>(cx: &'a 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
- "if {} == {}{} {}{}",
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{expr_block, snippet};
+use clippy_utils::ty::{implements_trait, match_type, peel_mid_ty_refs};
+use clippy_utils::{
+ is_lint_allowed, is_unit_expr, is_wild, paths, 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 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!(
- els_str,
++ "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)),
- "if let {} = {} {}{}",
+ );
+ (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!(
- els_str,
++ "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<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
+ // list of candidate `Enum`s we know will never get any more members
+ let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT];
+
+ for candidate_ty in candidates {
+ if match_type(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
- use clippy_utils::{get_parent_expr, is_lang_ctor, match_def_path, paths};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
- if let ExprKind::Path(ref err_fun_path) = err_fun.kind;
- if is_lang_ctor(cx, err_fun_path, ResultErr);
++use clippy_utils::{get_parent_expr, is_res_lang_ctor, match_def_path, path_res, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::ResultErr;
+use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::{hygiene, sym};
+
+use super::TRY_ERR;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutinee: &'tcx Expr<'_>) {
+ // Looks for a structure like this:
+ // match ::std::ops::Try::into_result(Err(5)) {
+ // ::std::result::Result::Err(err) =>
+ // #[allow(unreachable_code)]
+ // return ::std::ops::Try::from_error(::std::convert::From::from(err)),
+ // ::std::result::Result::Ok(val) =>
+ // #[allow(unreachable_code)]
+ // val,
+ // };
+ if_chain! {
+ if let ExprKind::Call(match_fun, [try_arg, ..]) = scrutinee.kind;
+ if let ExprKind::Path(ref match_fun_path) = match_fun.kind;
+ if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..));
+ if let ExprKind::Call(err_fun, [err_arg, ..]) = try_arg.kind;
- format!("{}{}{}{}", ret_prefix, prefix, origin_snippet, suffix)
++ if is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr);
+ if let Some(return_ty) = find_return_type(cx, &expr.kind);
+ then {
+ let prefix;
+ let suffix;
+ let err_ty;
+
+ if let Some(ty) = result_error_type(cx, return_ty) {
+ prefix = "Err(";
+ suffix = ")";
+ err_ty = ty;
+ } else if let Some(ty) = poll_result_error_type(cx, return_ty) {
+ prefix = "Poll::Ready(Err(";
+ suffix = "))";
+ err_ty = ty;
+ } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) {
+ prefix = "Poll::Ready(Some(Err(";
+ suffix = ")))";
+ err_ty = ty;
+ } else {
+ return;
+ };
+
+ let expr_err_ty = cx.typeck_results().expr_ty(err_arg);
+ let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt());
+ let mut applicability = Applicability::MachineApplicable;
+ let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability);
+ let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) {
+ "" // already returns
+ } else {
+ "return "
+ };
+ let suggestion = if err_ty == expr_err_ty {
- format!("{}{}{}.into(){}", ret_prefix, prefix, origin_snippet, suffix)
++ format!("{ret_prefix}{prefix}{origin_snippet}{suffix}")
+ } else {
++ format!("{ret_prefix}{prefix}{origin_snippet}.into(){suffix}")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ TRY_ERR,
+ expr.span,
+ "returning an `Err(_)` with the `?` operator",
+ "try this",
+ suggestion,
+ applicability,
+ );
+ }
+ }
+}
+
+/// Finds function return type by examining return expressions in match arms.
+fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> {
+ if let ExprKind::Match(_, arms, MatchSource::TryDesugar) = expr {
+ for arm in arms.iter() {
+ if let ExprKind::Ret(Some(ret)) = arm.body.kind {
+ return Some(cx.typeck_results().expr_ty(ret));
+ }
+ }
+ }
+ None
+}
+
+/// Extracts the error type from Result<T, E>.
+fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ if_chain! {
+ if let ty::Adt(_, subst) = ty.kind();
+ if is_type_diagnostic_item(cx, ty, sym::Result);
+ then {
+ Some(subst.type_at(1))
+ } else {
+ None
+ }
+ }
+}
+
+/// Extracts the error type from Poll<Result<T, E>>.
+fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ if_chain! {
+ if let ty::Adt(def, subst) = ty.kind();
+ if match_def_path(cx, def.did(), &paths::POLL);
+ let ready_ty = subst.type_at(0);
+
+ if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
+ if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did());
+ then {
+ Some(ready_subst.type_at(1))
+ } else {
+ None
+ }
+ }
+}
+
+/// Extracts the error type from Poll<Option<Result<T, E>>>.
+fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ if_chain! {
+ if let ty::Adt(def, subst) = ty.kind();
+ if match_def_path(cx, def.did(), &paths::POLL);
+ let ready_ty = subst.type_at(0);
+
+ if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
+ if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did());
+ let some_ty = ready_subst.type_at(0);
+
+ if let ty::Adt(some_def, some_subst) = some_ty.kind();
+ if cx.tcx.is_diagnostic_item(sym::Result, some_def.did());
+ then {
+ Some(some_subst.type_at(1))
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{is_default_equivalent, is_lang_ctor, meets_msrv, msrvs};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::ty::is_non_aggregate_primitive_type;
- if let ExprKind::Path(ref replacement_qpath) = src.kind {
- // Check that second argument is `Option::None`
- if is_lang_ctor(cx, replacement_qpath, OptionNone) {
- // Since this is a late pass (already type-checked),
- // and we already know that the second argument is an
- // `Option`, we do not need to check the first
- // argument's type. All that's left is to get
- // replacee's path.
- let replaced_path = match dest.kind {
- ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
- if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
- replaced_path
- } else {
- return;
- }
- },
- ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
- _ => return,
- };
++use clippy_utils::{is_default_equivalent, is_res_lang_ctor, meets_msrv, msrvs, path_res};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `mem::replace()` on an `Option` with
+ /// `None`.
+ ///
+ /// ### Why is this bad?
+ /// `Option` already has the method `take()` for
+ /// taking its current value (Some(..) or None) and replacing it with
+ /// `None`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::mem;
+ ///
+ /// let mut an_option = Some(0);
+ /// let replaced = mem::replace(&mut an_option, None);
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// let mut an_option = Some(0);
+ /// let taken = an_option.take();
+ /// ```
+ #[clippy::version = "1.31.0"]
+ pub MEM_REPLACE_OPTION_WITH_NONE,
+ style,
+ "replacing an `Option` with `None` instead of `take()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `mem::replace(&mut _, mem::uninitialized())`
+ /// and `mem::replace(&mut _, mem::zeroed())`.
+ ///
+ /// ### Why is this bad?
+ /// This will lead to undefined behavior even if the
+ /// value is overwritten later, because the uninitialized value may be
+ /// observed in the case of a panic.
+ ///
+ /// ### Example
+ /// ```
+ /// use std::mem;
+ ///# fn may_panic(v: Vec<i32>) -> Vec<i32> { v }
+ ///
+ /// #[allow(deprecated, invalid_value)]
+ /// fn myfunc (v: &mut Vec<i32>) {
+ /// let taken_v = unsafe { mem::replace(v, mem::uninitialized()) };
+ /// let new_v = may_panic(taken_v); // undefined behavior on panic
+ /// mem::forget(mem::replace(v, new_v));
+ /// }
+ /// ```
+ ///
+ /// The [take_mut](https://docs.rs/take_mut) crate offers a sound solution,
+ /// at the cost of either lazily creating a replacement value or aborting
+ /// on panic, to ensure that the uninitialized value cannot be observed.
+ #[clippy::version = "1.39.0"]
+ pub MEM_REPLACE_WITH_UNINIT,
+ correctness,
+ "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `std::mem::replace` on a value of type
+ /// `T` with `T::default()`.
+ ///
+ /// ### Why is this bad?
+ /// `std::mem` module already has the method `take` to
+ /// take the current value and replace it with the default value of that type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut text = String::from("foo");
+ /// let replaced = std::mem::replace(&mut text, String::default());
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// let mut text = String::from("foo");
+ /// let taken = std::mem::take(&mut text);
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub MEM_REPLACE_WITH_DEFAULT,
+ style,
+ "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`"
+}
+
+impl_lint_pass!(MemReplace =>
+ [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]);
+
+fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
- let mut applicability = Applicability::MachineApplicable;
- span_lint_and_sugg(
- cx,
- MEM_REPLACE_OPTION_WITH_NONE,
- expr_span,
- "replacing an `Option` with `None`",
- "consider `Option::take()` instead",
- format!(
- "{}.take()",
- snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
- ),
- applicability,
- );
- }
++ // Check that second argument is `Option::None`
++ if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
++ // Since this is a late pass (already type-checked),
++ // and we already know that the second argument is an
++ // `Option`, we do not need to check the first
++ // argument's type. All that's left is to get
++ // replacee's path.
++ let replaced_path = match dest.kind {
++ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
++ if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
++ replaced_path
++ } else {
++ return;
++ }
++ },
++ ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
++ _ => return,
++ };
+
- if let ExprKind::Path(q) = &src.kind {
- if is_lang_ctor(cx, q, OptionNone) {
- return;
- }
++ let mut applicability = Applicability::MachineApplicable;
++ span_lint_and_sugg(
++ cx,
++ MEM_REPLACE_OPTION_WITH_NONE,
++ expr_span,
++ "replacing an `Option` with `None`",
++ "consider `Option::take()` instead",
++ format!(
++ "{}.take()",
++ snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
++ ),
++ applicability,
++ );
+ }
+}
+
+fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ if_chain! {
+ // check if replacement is mem::MaybeUninit::uninit().assume_init()
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(src.hir_id);
+ if cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::MaybeUninit::uninit().assume_init()`",
+ "consider using",
+ format!(
+ "std::ptr::read({})",
+ snippet_with_applicability(cx, dest.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ return;
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Call(repl_func, []) = src.kind;
+ if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
+ if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
+ then {
+ if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::uninitialized()`",
+ "consider using",
+ format!(
+ "std::ptr::read({})",
+ snippet_with_applicability(cx, dest.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) &&
+ !cx.typeck_results().expr_ty(src).is_primitive() {
+ span_lint_and_help(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::zeroed()`",
+ None,
+ "consider using a default value or the `take_mut` crate instead",
+ );
+ }
+ }
+ }
+}
+
+fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ // disable lint for primitives
+ let expr_type = cx.typeck_results().expr_ty_adjusted(src);
+ if is_non_aggregate_primitive_type(expr_type) {
+ return;
+ }
+ // disable lint for Option since it is covered in another lint
++ if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
++ return;
+ }
+ if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
+ span_lint_and_then(
+ cx,
+ MEM_REPLACE_WITH_DEFAULT,
+ expr_span,
+ "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
+ |diag| {
+ if !expr_span.from_expansion() {
+ let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
+
+ diag.span_suggestion(
+ expr_span,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+}
+
+pub struct MemReplace {
+ msrv: Option<RustcVersion>,
+}
+
+impl MemReplace {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MemReplace {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // Check that `expr` is a call to `mem::replace()`
+ if let ExprKind::Call(func, [dest, src]) = expr.kind;
+ if let ExprKind::Path(ref func_qpath) = func.kind;
+ if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
+ if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
+ then {
+ check_replace_option_with_none(cx, src, dest, expr.span);
+ check_replace_with_uninit(cx, src, dest, expr.span);
+ if meets_msrv(self.msrv, msrvs::MEM_TAKE) {
+ check_replace_with_default(cx, src, dest, expr.span);
+ }
+ }
+ }
+ }
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
+use super::{contains_return, BIND_INSTEAD_OF_MAP};
+use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{LangItem, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty::DefIdTree;
+use rustc_span::Span;
+
+pub(crate) struct OptionAndThenSome;
+
+impl BindInsteadOfMap for OptionAndThenSome {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome;
+ const BAD_METHOD_NAME: &'static str = "and_then";
+ const GOOD_METHOD_NAME: &'static str = "map";
+}
+
+pub(crate) struct ResultAndThenOk;
+
+impl BindInsteadOfMap for ResultAndThenOk {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk;
+ const BAD_METHOD_NAME: &'static str = "and_then";
+ const GOOD_METHOD_NAME: &'static str = "map";
+}
+
+pub(crate) struct ResultOrElseErrInfo;
+
+impl BindInsteadOfMap for ResultOrElseErrInfo {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr;
+ const BAD_METHOD_NAME: &'static str = "or_else";
+ const GOOD_METHOD_NAME: &'static str = "map_err";
+}
+
+pub(crate) trait BindInsteadOfMap {
+ const VARIANT_LANG_ITEM: LangItem;
+ const BAD_METHOD_NAME: &'static str;
+ const GOOD_METHOD_NAME: &'static str;
+
+ fn no_op_msg(cx: &LateContext<'_>) -> Option<String> {
+ let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
+ let item_id = cx.tcx.parent(variant_id);
+ Some(format!(
+ "using `{}.{}({})`, which is a no-op",
+ cx.tcx.item_name(item_id),
+ Self::BAD_METHOD_NAME,
+ cx.tcx.item_name(variant_id),
+ ))
+ }
+
+ fn lint_msg(cx: &LateContext<'_>) -> Option<String> {
+ let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
+ let item_id = cx.tcx.parent(variant_id);
+ Some(format!(
+ "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
+ cx.tcx.item_name(item_id),
+ Self::BAD_METHOD_NAME,
+ cx.tcx.item_name(variant_id),
+ Self::GOOD_METHOD_NAME
+ ))
+ }
+
+ fn lint_closure_autofixable(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
+ closure_expr: &hir::Expr<'_>,
+ closure_args_span: Span,
+ ) -> bool {
+ if_chain! {
+ if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind;
+ if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind;
+ if Self::is_variant(cx, path.res);
+ if !contains_return(inner_expr);
+ if let Some(msg) = Self::lint_msg(cx);
+ then {
+ let some_inner_snip = if inner_expr.span.from_expansion() {
+ snippet_with_macro_callsite(cx, inner_expr.span, "_")
+ } else {
+ snippet(cx, inner_expr.span, "_")
+ };
+
+ let closure_args_snip = snippet(cx, closure_args_span, "..");
+ let option_snip = snippet(cx, recv.span, "..");
++ let note = format!("{option_snip}.{}({closure_args_snip} {some_inner_snip})", Self::GOOD_METHOD_NAME);
+ span_lint_and_sugg(
+ cx,
+ BIND_INSTEAD_OF_MAP,
+ expr.span,
+ &msg,
+ "try this",
+ note,
+ Applicability::MachineApplicable,
+ );
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
+ let mut suggs = Vec::new();
+ let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
+ if_chain! {
+ if !ret_expr.span.from_expansion();
+ if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
+ if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
+ if Self::is_variant(cx, path.res);
+ if !contains_return(arg);
+ then {
+ suggs.push((ret_expr.span, arg.span.source_callsite()));
+ true
+ } else {
+ false
+ }
+ }
+ });
+ let (span, msg) = if_chain! {
+ if can_sugg;
+ if let hir::ExprKind::MethodCall(segment, ..) = expr.kind;
+ if let Some(msg) = Self::lint_msg(cx);
+ then { (segment.ident.span, msg) } else { return false; }
+ };
+ span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| {
+ multispan_sugg_with_applicability(
+ diag,
+ "try this",
+ Applicability::MachineApplicable,
+ std::iter::once((span, Self::GOOD_METHOD_NAME.into())).chain(
+ suggs
+ .into_iter()
+ .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
+ ),
+ );
+ });
+ true
+ }
+
+ /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
+ fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
+ if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
+ if adt.did() == cx.tcx.parent(vid);
+ then {} else { return false; }
+ }
+
+ match arg.kind {
+ hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) => {
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(closure_body.value);
+
+ if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, fn_decl_span) {
+ true
+ } else {
+ Self::lint_closure(cx, expr, closure_expr)
+ }
+ },
+ // `_.and_then(Some)` case, which is no-op.
+ hir::ExprKind::Path(QPath::Resolved(_, path)) if Self::is_variant(cx, path.res) => {
+ if let Some(msg) = Self::no_op_msg(cx) {
+ span_lint_and_sugg(
+ cx,
+ BIND_INSTEAD_OF_MAP,
+ expr.span,
+ &msg,
+ "use the expression directly",
+ snippet(cx, recv.span, "..").into(),
+ Applicability::MachineApplicable,
+ );
+ }
+ true
+ },
+ _ => false,
+ }
+ }
+
+ fn is_variant(cx: &LateContext<'_>, res: Res) -> bool {
+ if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
+ if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) {
+ return cx.tcx.parent(id) == variant_id;
+ }
+ }
+ false
+ }
+}
--- /dev/null
- &format!("called `.bytes().nth()` on a `{}`", caller_type),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::BYTES_NTH;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) {
+ let ty = cx.typeck_results().expr_ty(recv).peel_refs();
+ let caller_type = if ty.is_str() {
+ "str"
+ } else if is_type_diagnostic_item(cx, ty, sym::String) {
+ "String"
+ } else {
+ return;
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BYTES_NTH,
+ expr.span,
++ &format!("called `.bytes().nth()` on a `{caller_type}`"),
+ "try",
+ format!(
+ "{}.as_bytes().get({})",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ snippet_with_applicability(cx, n_arg.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+}
--- /dev/null
- &format!("you should use the `{}` method", suggest),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{method_chain_args, path_def_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_lint::Lint;
+use rustc_middle::ty::{self, DefIdTree};
+
+/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+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::Call(fun, [arg_char]) = info.other.kind;
+ if let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id));
+ if Some(id) == cx.tcx.lang_items().option_some_variant();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let self_ty = cx.typeck_results().expr_ty_adjusted(args[0].0).peel_refs();
+
+ if *self_ty.kind() != ty::Str {
+ return false;
+ }
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ info.expr.span,
- format!("{}{}.{}({})",
++ &format!("you should use the `{suggest}` method"),
+ "like this",
- suggest,
++ format!("{}{}.{suggest}({})",
+ if info.eq { "" } else { "!" },
+ snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability),
+ snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)),
+ applicability,
+ );
+
+ return true;
+ }
+ }
+
+ false
+}
--- /dev/null
- &format!("you should use the `{}` method", suggest),
+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<'tcx>(
+ cx: &LateContext<'tcx>,
+ 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!("{}{}.{}('{}')",
++ &format!("you should use the `{suggest}` method"),
+ "like this",
- suggest,
++ 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
- this will copy the reference of type `{}` instead of cloning the inner type",
- ty
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::get_parent_node;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::sugg;
+use clippy_utils::ty::is_copy;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, MatchSource, Node, PatKind, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, adjustment::Adjust};
+use rustc_span::symbol::{sym, Symbol};
+
+use super::CLONE_DOUBLE_REF;
+use super::CLONE_ON_COPY;
+
+/// Checks for the `CLONE_ON_COPY` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
+ args: &[Expr<'_>],
+) {
+ let arg = if method_name == sym::clone && args.is_empty() {
+ receiver
+ } else {
+ return;
+ };
+ if cx
+ .typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .and_then(|id| cx.tcx.trait_of_item(id))
+ .zip(cx.tcx.lang_items().clone_trait())
+ .map_or(true, |(x, y)| x != y)
+ {
+ return;
+ }
+ let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
+ let arg_ty = arg_adjustments
+ .last()
+ .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target);
+
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Ref(_, inner, _) = arg_ty.kind() {
+ if let ty::Ref(_, innermost, _) = inner.kind() {
+ span_lint_and_then(
+ cx,
+ CLONE_DOUBLE_REF,
+ expr.span,
+ &format!(
+ "using `clone` on a double-reference; \
- let explicit = format!("<{}{}>::clone({})", refs, ty, snip);
++ this will copy the reference of type `{ty}` instead of cloning the inner type"
+ ),
+ |diag| {
+ if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) {
+ let mut ty = innermost;
+ let mut n = 0;
+ while let ty::Ref(_, inner, _) = ty.kind() {
+ ty = inner;
+ n += 1;
+ }
+ let refs = "&".repeat(n + 1);
+ let derefs = "*".repeat(n);
- format!("{}({}{}).clone()", refs, derefs, snip.deref()),
++ let explicit = format!("<{refs}{ty}>::clone({snip})");
+ diag.span_suggestion(
+ expr.span,
+ "try dereferencing it",
- ("try dereferencing it", format!("({}{})", "*".repeat(deref_count), snip))
++ format!("{refs}({derefs}{}).clone()", snip.deref()),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or try being explicit if you are sure, that you want to clone a reference",
+ explicit,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ return; // don't report clone_on_copy
+ }
+ }
+
+ if is_copy(cx, ty) {
+ let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
+ Some(Node::Expr(parent)) => match parent.kind {
+ // &*x is a nop, &x.clone() is not
+ ExprKind::AddrOf(..) => return,
+ // (*x).func() is useless, x.clone().func() can work in case func borrows self
+ ExprKind::MethodCall(_, self_arg, ..)
+ if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) =>
+ {
+ return;
+ },
+ // ? is a Call, makes sure not to rec *x?, but rather (*x)?
+ ExprKind::Call(hir_callee, _) => matches!(
+ hir_callee.kind,
+ ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, _, _))
+ ),
+ ExprKind::MethodCall(_, self_arg, ..) if expr.hir_id == self_arg.hir_id => true,
+ ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
+ | ExprKind::Field(..)
+ | ExprKind::Index(..) => true,
+ _ => false,
+ },
+ // local binding capturing a reference
+ Some(Node::Local(l)) if matches!(l.pat.kind, PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..)) => {
+ return;
+ },
+ _ => false,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0;
+
+ let deref_count = arg_adjustments
+ .iter()
+ .take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ .count();
+ let (help, sugg) = if deref_count == 0 {
+ ("try removing the `clone` call", snip.into())
+ } else if parent_is_suffix_expr {
- ("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip))
++ ("try dereferencing it", format!("({}{snip})", "*".repeat(deref_count)))
+ } else {
- &format!("using `clone` on type `{}` which implements the `Copy` trait", ty),
++ ("try dereferencing it", format!("{}{snip}", "*".repeat(deref_count)))
+ };
+
+ span_lint_and_sugg(
+ cx,
+ CLONE_ON_COPY,
+ expr.span,
++ &format!("using `clone` on type `{ty}` which implements the `Copy` trait"),
+ help,
+ sugg,
+ app,
+ );
+ }
+}
--- /dev/null
- format!("{}::<{}>::clone(&{})", caller_type, subst.type_at(0), snippet),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::paths;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::ty::{is_type_diagnostic_item, match_type};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::{sym, Symbol};
+
+use super::CLONE_ON_REF_PTR;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ method_name: Symbol,
+ receiver: &hir::Expr<'_>,
+ args: &[hir::Expr<'_>],
+) {
+ if !(args.is_empty() && method_name == sym::clone) {
+ return;
+ }
+ let obj_ty = cx.typeck_results().expr_ty(receiver).peel_refs();
+
+ if let ty::Adt(_, subst) = obj_ty.kind() {
+ let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) {
+ "Rc"
+ } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) {
+ "Arc"
+ } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) {
+ "Weak"
+ } else {
+ return;
+ };
+
+ let snippet = snippet_with_macro_callsite(cx, receiver.span, "..");
+
+ span_lint_and_sugg(
+ cx,
+ CLONE_ON_REF_PTR,
+ expr.span,
+ "using `.clone()` on a ref-counted pointer",
+ "try this",
++ format!("{caller_type}::<{}>::clone(&{snippet})", subst.type_at(0)),
+ Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak
+ );
+ }
+}
--- /dev/null
- &format!("use of `{}` followed by a function call", name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use std::borrow::Cow;
+
+use super::EXPECT_FUN_CALL;
+
+/// Checks for the `EXPECT_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<'tcx>,
+ args: &'tcx [hir::Expr<'tcx>],
+) {
+ // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
+ // `&str`
+ fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
+ let mut arg_root = arg;
+ loop {
+ arg_root = match &arg_root.kind {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
+ hir::ExprKind::MethodCall(method_name, receiver, [], ..) => {
+ if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && {
+ let arg_type = cx.typeck_results().expr_ty(receiver);
+ let base_type = arg_type.peel_refs();
+ *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::String)
+ } {
+ receiver
+ } else {
+ break;
+ }
+ },
+ _ => break,
+ };
+ }
+ arg_root
+ }
+
+ // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be
+ // converted to string.
+ fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+ if is_type_diagnostic_item(cx, arg_ty, sym::String) {
+ return false;
+ }
+ if let ty::Ref(_, ty, ..) = arg_ty.kind() {
+ if *ty.kind() == ty::Str && can_be_static_str(cx, arg) {
+ return false;
+ }
+ };
+ true
+ }
+
+ // Check if an expression could have type `&'static str`, knowing that it
+ // has type `&str` for some lifetime.
+ fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
+ match arg.kind {
+ hir::ExprKind::Lit(_) => true,
+ hir::ExprKind::Call(fun, _) => {
+ if let hir::ExprKind::Path(ref p) = fun.kind {
+ match cx.qpath_res(p, fun.hir_id) {
+ hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
+ cx.tcx.fn_sig(def_id).output().skip_binder().kind(),
+ ty::Ref(re, ..) if re.is_static(),
+ ),
+ _ => false,
+ }
+ } else {
+ false
+ }
+ },
+ hir::ExprKind::MethodCall(..) => {
+ cx.typeck_results()
+ .type_dependent_def_id(arg.hir_id)
+ .map_or(false, |method_id| {
+ matches!(
+ cx.tcx.fn_sig(method_id).output().skip_binder().kind(),
+ ty::Ref(re, ..) if re.is_static()
+ )
+ })
+ },
+ hir::ExprKind::Path(ref p) => matches!(
+ cx.qpath_res(p, arg.hir_id),
+ hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static(_), _)
+ ),
+ _ => false,
+ }
+ }
+
+ fn is_call(node: &hir::ExprKind<'_>) -> bool {
+ match node {
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
+ is_call(&expr.kind)
+ },
+ hir::ExprKind::Call(..)
+ | hir::ExprKind::MethodCall(..)
+ // These variants are debatable or require further examination
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::Match(..)
+ | hir::ExprKind::Block{ .. } => true,
+ _ => false,
+ }
+ }
+
+ if args.len() != 1 || name != "expect" || !is_call(&args[0].kind) {
+ return;
+ }
+
+ let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver);
+ let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) {
+ "||"
+ } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) {
+ "|_|"
+ } else {
+ return;
+ };
+
+ let arg_root = get_arg_root(cx, &args[0]);
+
+ let span_replace_word = method_span.with_hi(expr.span.hi());
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ //Special handling for `format!` as arg_root
+ if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
+ if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
+ return;
+ }
+ let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
+ let span = format_args.inputs_span();
+ let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
- format!("unwrap_or_else({} panic!({}))", closure_args, sugg),
++ &format!("use of `{name}` followed by a function call"),
+ "try this",
- &format!("use of `{}` followed by a function call", name),
++ format!("unwrap_or_else({closure_args} panic!({sugg}))"),
+ applicability,
+ );
+ return;
+ }
+
+ let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability);
+ if requires_to_string(cx, arg_root) {
+ arg_root_snippet.to_mut().push_str(".to_string()");
+ }
+
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
- format!(
- "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})",
- closure_args, arg_root_snippet
- ),
++ &format!("use of `{name}` followed by a function call"),
+ "try this",
++ format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"),
+ applicability,
+ );
+}
--- /dev/null
- use clippy_utils::ty::match_type;
- use clippy_utils::{get_parent_expr, paths};
+use clippy_utils::diagnostics::span_lint_and_help;
- if !match_type(cx, ty, &paths::FILE_TYPE) {
++use clippy_utils::get_parent_expr;
++use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
++use rustc_span::sym;
+
+use super::FILETYPE_IS_FILE;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(recv);
+
- let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb);
- let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary);
++ if !is_type_diagnostic_item(cx, ty, sym::FileType) {
+ return;
+ }
+
+ let span: Span;
+ let verb: &str;
+ let lint_unary: &str;
+ let help_unary: &str;
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::Unary(op, _) = parent.kind;
+ if op == hir::UnOp::Not;
+ then {
+ lint_unary = "!";
+ verb = "denies";
+ help_unary = "";
+ span = parent.span;
+ } else {
+ lint_unary = "";
+ verb = "covers";
+ help_unary = "!";
+ span = expr.span;
+ }
+ }
++ let lint_msg = format!("`{lint_unary}FileType::is_file()` only {verb} regular files");
++ let help_msg = format!("use `{help_unary}FileType::is_dir()` instead");
+ span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg);
+}
--- /dev/null
- format!("{}.find_map({})", iter_snippet, filter_snippet),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::{is_trait_method, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+use rustc_span::sym;
+
+use super::FILTER_MAP_NEXT;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ if !meets_msrv(msrv, msrvs::ITERATOR_FIND_MAP) {
+ return;
+ }
+
+ let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find_map(..)` instead";
+ let filter_snippet = snippet(cx, arg.span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ let iter_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ FILTER_MAP_NEXT,
+ expr.span,
+ msg,
+ "try this",
++ format!("{iter_snippet}.find_map({filter_snippet})"),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, FILTER_MAP_NEXT, expr.span, msg);
+ }
+ }
+}
--- /dev/null
- format!("{}.find({})", iter_snippet, filter_snippet),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::implements_trait;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::FILTER_NEXT;
+
+/// lint use of `filter().next()` for `Iterators`
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ filter_arg: &'tcx hir::Expr<'_>,
+) {
+ // lint if caller of `.filter().next()` is an Iterator
+ let recv_impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
+ implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])
+ });
+ if recv_impls_iterator {
+ let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
+ `.find(..)` instead";
+ let filter_snippet = snippet(cx, filter_arg.span, "..");
+ if filter_snippet.lines().count() <= 1 {
+ let iter_snippet = snippet(cx, recv.span, "..");
+ // add note if not multi-line
+ span_lint_and_sugg(
+ cx,
+ FILTER_NEXT,
+ expr.span,
+ msg,
+ "try this",
++ format!("{iter_snippet}.find({filter_snippet})"),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, FILTER_NEXT, expr.span, msg);
+ }
+ }
+}
--- /dev/null
- let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish);
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{is_expr_path_def_path, paths, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::sym;
+
+use super::FROM_ITER_INSTEAD_OF_COLLECT;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) {
+ if_chain! {
+ if is_expr_path_def_path(cx, func, &paths::FROM_ITERATOR_METHOD);
+ let ty = cx.typeck_results().expr_ty(expr);
+ let arg_ty = cx.typeck_results().expr_ty(&args[0]);
+ if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+
+ if implements_trait(cx, arg_ty, iter_id, &[]);
+ then {
+ // `expr` implements `FromIterator` trait
+ let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();
+ let turbofish = extract_turbofish(cx, expr, ty);
- format!("{}{}", without_ts.join("::"), type_specifier)
++ let sugg = format!("{iter_expr}.collect::<{turbofish}>()");
+ span_lint_and_sugg(
+ cx,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ expr.span,
+ "usage of `FromIterator::from_iter`",
+ "use `.collect()` instead of `::from_iter()`",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+}
+
+fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'_>) -> String {
+ fn strip_angle_brackets(s: &str) -> Option<&str> {
+ s.strip_prefix('<')?.strip_suffix('>')
+ }
+
+ let call_site = expr.span.source_callsite();
+ if_chain! {
+ if let Some(snippet) = snippet_opt(cx, call_site);
+ let snippet_split = snippet.split("::").collect::<Vec<_>>();
+ if let Some((_, elements)) = snippet_split.split_last();
+
+ then {
+ if_chain! {
+ if let [type_specifier, _] = snippet_split.as_slice();
+ if let Some(type_specifier) = strip_angle_brackets(type_specifier);
+ if let Some((type_specifier, ..)) = type_specifier.split_once(" as ");
+ then {
+ type_specifier.to_string()
+ } else {
+ // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`)
+ if let Some(type_specifier) = snippet_split.iter().find(|e| strip_angle_brackets(e).is_some()) {
+ // remove the type specifier from the path elements
+ let without_ts = elements.iter().filter_map(|e| {
+ if e == type_specifier { None } else { Some((*e).to_string()) }
+ }).collect::<Vec<_>>();
+ // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`)
- format!("{}<{}>", elements.join("::"), wildcards)
++ format!("{}{type_specifier}", without_ts.join("::"))
+ } else {
+ // type is not explicitly specified so wildcards are needed
+ // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
+ let ty_str = ty.to_string();
+ let start = ty_str.find('<').unwrap_or(0);
+ let end = ty_str.find('>').unwrap_or(ty_str.len());
+ let nb_wildcard = ty_str[start..end].split(',').count();
+ let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
++ format!("{}<{wildcards}>", elements.join("::"))
+ }
+ }
+ }
+ } else {
+ ty.to_string()
+ }
+ }
+}
--- /dev/null
- &format!("accessing first element with `{0}.get(0)`", slice_name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_slice_of_primitives;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::source_map::Spanned;
+
+use super::GET_FIRST;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+) {
+ if_chain! {
+ if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(method_id);
+ if cx.tcx.type_of(impl_id).is_slice();
+ if let Some(_) = is_slice_of_primitives(cx, recv);
+ if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind;
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let slice_name = snippet_with_applicability(cx, recv.span, "..", &mut app);
+ span_lint_and_sugg(
+ cx,
+ GET_FIRST,
+ expr.span,
- format!("{}.first()", slice_name),
++ &format!("accessing first element with `{slice_name}.get(0)`"),
+ "try",
++ format!("{slice_name}.first()"),
+ app,
+ );
+ }
+ }
+}
--- /dev/null
- use clippy_utils::SpanlessEq;
- use rustc_ast::LitKind;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
- && let ExprKind::Lit(rhs_lit) = &rhs.kind
- && let LitKind::Int(1, ..) = rhs_lit.node
++use clippy_utils::{is_integer_literal, SpanlessEq};
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Spanned;
+use rustc_span::sym;
+
+use super::GET_LAST_WITH_LEN;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
+ // Argument to "get" is a subtraction
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub, ..
+ },
+ lhs,
+ rhs,
+ ) = arg.kind
+
+ // LHS of subtraction is "x.len()"
+ && let ExprKind::MethodCall(lhs_path, lhs_recv, [], _) = &lhs.kind
+ && lhs_path.ident.name == sym::len
+
+ // RHS of subtraction is 1
++ && is_integer_literal(rhs, 1)
+
+ // check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)`
+ && SpanlessEq::new(cx).eq_expr(recv, lhs_recv)
+ && !recv.can_have_side_effects()
+ {
+ let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() {
+ ty::Adt(def, _) if cx.tcx.is_diagnostic_item(sym::VecDeque, def.did()) => "back",
+ ty::Slice(_) => "last",
+ _ => return,
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ let recv_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability);
+
+ span_lint_and_sugg(
+ cx,
+ GET_LAST_WITH_LEN,
+ expr.span,
+ &format!("accessing last element with `{recv_snippet}.get({recv_snippet}.len() - 1)`"),
+ "try",
+ format!("{recv_snippet}.{method}()"),
+ applicability,
+ );
+ }
+}
--- /dev/null
- &format!(
- "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise",
- mut_str, caller_type
- ),
+use super::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::get_parent_expr;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::GET_UNWRAP;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'tcx>,
+ get_arg: &'tcx hir::Expr<'_>,
+ is_mut: bool,
+) {
+ // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`,
+ // because they do not implement `IndexMut`
+ let mut applicability = Applicability::MachineApplicable;
+ let expr_ty = cx.typeck_results().expr_ty(recv);
+ let get_args_str = snippet_with_applicability(cx, get_arg.span, "..", &mut applicability);
+ let mut needs_ref;
+ let caller_type = if derefs_to_slice(cx, recv, expr_ty).is_some() {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "slice"
+ } else if is_type_diagnostic_item(cx, expr_ty, sym::Vec) {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "Vec"
+ } else if is_type_diagnostic_item(cx, expr_ty, sym::VecDeque) {
+ needs_ref = get_args_str.parse::<usize>().is_ok();
+ "VecDeque"
+ } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::HashMap) {
+ needs_ref = true;
+ "HashMap"
+ } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::BTreeMap) {
+ needs_ref = true;
+ "BTreeMap"
+ } else {
+ return; // caller is not a type that we want to lint
+ };
+
+ let mut span = expr.span;
+
+ // Handle the case where the result is immediately dereferenced
+ // by not requiring ref and pulling the dereference into the
+ // suggestion.
+ if_chain! {
+ if needs_ref;
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind;
+ then {
+ needs_ref = false;
+ span = parent.span;
+ }
+ }
+
+ let mut_str = if is_mut { "_mut" } else { "" };
+ let borrow_str = if !needs_ref {
+ ""
+ } else if is_mut {
+ "&mut "
+ } else {
+ "&"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ GET_UNWRAP,
+ span,
- "{}{}[{}]",
- borrow_str,
- snippet_with_applicability(cx, recv.span, "..", &mut applicability),
- get_args_str
++ &format!("called `.get{mut_str}().unwrap()` on a {caller_type}. Using `[]` is more clear and more concise"),
+ "try this",
+ format!(
++ "{borrow_str}{}[{get_args_str}]",
++ snippet_with_applicability(cx, recv.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+}
--- /dev/null
- &format!("implicitly cloning a `{}` by calling `{}` on its dereferenced type", ty_name, method_name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::peel_mid_ty_refs;
+use clippy_utils::{is_diag_item_method, is_diag_trait_item};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::IMPLICIT_CLONE;
+
+pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
+ if_chain! {
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if is_clone_like(cx, method_name, method_def_id);
+ let return_type = cx.typeck_results().expr_ty(expr);
+ let input_type = cx.typeck_results().expr_ty(recv);
+ let (input_type, ref_count) = peel_mid_ty_refs(input_type);
+ if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did()));
+ if return_type == input_type;
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_CLONE,
+ expr.span,
- format!("({}{}).clone()", "*".repeat(ref_count - 1), recv_snip)
++ &format!("implicitly cloning a `{ty_name}` by calling `{method_name}` on its dereferenced type"),
+ "consider using",
+ if ref_count > 1 {
- format!("{}.clone()", recv_snip)
++ format!("({}{recv_snip}).clone()", "*".repeat(ref_count - 1))
+ } else {
++ format!("{recv_snip}.clone()")
+ },
+ app,
+ );
+ }
+ }
+}
+
+/// Returns true if the named method can be used to clone the receiver.
+/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call
+/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g.,
+/// `is_to_owned_like` in `unnecessary_to_owned.rs`.
+pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir::def_id::DefId) -> bool {
+ match method_name {
+ "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr),
+ "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
+ "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path),
+ "to_vec" => cx
+ .tcx
+ .impl_of_method(method_def_id)
+ .filter(|&impl_did| cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none())
+ .is_some(),
+ _ => false,
+ }
+}
--- /dev/null
- &format!("calling `to_string` on `{}`", arg_ty),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_type_diagnostic_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::{sym, Symbol};
+
+use super::INEFFICIENT_TO_STRING;
+
+/// Checks for the `INEFFICIENT_TO_STRING` lint
+pub fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ 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,
- "`{}` implements `ToString` through a slower blanket impl, but `{}` has a fast specialization of `ToString`",
- self_ty, deref_self_ty
++ &format!("calling `to_string` on `{arg_ty}`"),
+ |diag| {
+ diag.help(&format!(
- format!("({}{}).to_string()", "*".repeat(deref_count), arg_snippet),
++ "`{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",
- match_def_path(cx, adt.did(), &paths::COW) && substs.type_at(1).is_str()
++ 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_diagnostic_item(cx, ty, sym::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
- "this `.into_iter()` call is equivalent to `.{}()` and will not consume the `{}`",
- method_name, kind,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::ty::has_iter_method;
+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::source_map::Span;
+use rustc_span::symbol::{sym, Symbol};
+
+use super::INTO_ITER_ON_REF;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ method_name: Symbol,
+ receiver: &hir::Expr<'_>,
+) {
+ let self_ty = cx.typeck_results().expr_ty_adjusted(receiver);
+ if_chain! {
+ if let ty::Ref(..) = self_ty.kind();
+ if method_name == sym::into_iter;
+ if is_trait_method(cx, expr, sym::IntoIterator);
+ if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ty);
+ then {
+ span_lint_and_sugg(
+ cx,
+ INTO_ITER_ON_REF,
+ method_span,
+ &format!(
++ "this `.into_iter()` call is equivalent to `.{method_name}()` and will not consume the `{kind}`",
+ ),
+ "call directly",
+ method_name.to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(Symbol, &'static str)> {
+ has_iter_method(cx, self_ref_ty).map(|ty_name| {
+ let mutbl = match self_ref_ty.kind() {
+ ty::Ref(_, _, mutbl) => mutbl,
+ _ => unreachable!(),
+ };
+ let method_name = match mutbl {
+ hir::Mutability::Not => "iter",
+ hir::Mutability::Mut => "iter_mut",
+ };
+ (ty_name, method_name)
+ })
+}
--- /dev/null
- &format!("use of `char::is_digit` with literal radix of {}", num),
+//! Lint for `c.is_digit(10)`
+
+use super::IS_DIGIT_ASCII_RADIX;
+use clippy_utils::{
+ consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs,
+ source::snippet_with_applicability,
+};
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ self_arg: &'tcx Expr<'_>,
+ radix: &'tcx Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if !meets_msrv(msrv, msrvs::IS_ASCII_DIGIT) {
+ return;
+ }
+
+ if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_char() {
+ return;
+ }
+
+ if let Some(radix_val) = constant_full_int(cx, cx.typeck_results(), radix) {
+ let (num, replacement) = match radix_val {
+ FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
+ FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),
+ _ => return,
+ };
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_sugg(
+ cx,
+ IS_DIGIT_ASCII_RADIX,
+ expr.span,
- "{}.{}()",
- snippet_with_applicability(cx, self_arg.span, "..", &mut applicability),
- replacement
++ &format!("use of `char::is_digit` with literal radix of {num}"),
+ "try",
+ format!(
++ "{}.{replacement}()",
++ snippet_with_applicability(cx, self_arg.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
- &format!("called `iter().{}().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
- more readable", method_name),
+use crate::methods::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_CLONED_COLLECT;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, method_name: &str, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec);
+ if let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv));
+ if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite());
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ITER_CLONED_COLLECT,
+ to_replace,
++ &format!("called `iter().{method_name}().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
++ more readable"),
+ "try",
+ ".to_vec()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- &format!("called `.{}().count()` on a `{}`", iter_method, caller_type),
+use super::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_COUNT;
+
+pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: &str) {
+ let ty = cx.typeck_results().expr_ty(recv);
+ let caller_type = if derefs_to_slice(cx, recv, ty).is_some() {
+ "slice"
+ } else if is_type_diagnostic_item(cx, ty, sym::Vec) {
+ "Vec"
+ } else if is_type_diagnostic_item(cx, ty, sym::VecDeque) {
+ "VecDeque"
+ } else if is_type_diagnostic_item(cx, ty, sym::HashSet) {
+ "HashSet"
+ } else if is_type_diagnostic_item(cx, ty, sym::HashMap) {
+ "HashMap"
+ } else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
+ "BTreeMap"
+ } else if is_type_diagnostic_item(cx, ty, sym::BTreeSet) {
+ "BTreeSet"
+ } else if is_type_diagnostic_item(cx, ty, sym::LinkedList) {
+ "LinkedList"
+ } else if is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
+ "BinaryHeap"
+ } else {
+ return;
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_COUNT,
+ expr.span,
++ &format!("called `.{iter_method}().count()` on a `{caller_type}`"),
+ "try",
+ format!(
+ "{}.len()",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+}
--- /dev/null
- &format!("iterating on a map's {}s", replacement_kind),
+#![allow(unused_imports)]
+
+use super::ITER_KV_MAP;
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::is_local_used;
+use rustc_hir::{BindingAnnotation, Body, BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::ty;
+use rustc_span::sym;
+use rustc_span::Span;
+
+/// lint use of:
+/// - `hashmap.iter().map(|(_, v)| v)`
+/// - `hashmap.into_iter().map(|(_, v)| v)`
+/// on `HashMaps` and `BTreeMaps` in std
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ map_type: &'tcx str, // iter / into_iter
+ expr: &'tcx Expr<'tcx>, // .iter().map(|(_, v_| v))
+ recv: &'tcx Expr<'tcx>, // hashmap
+ m_arg: &'tcx Expr<'tcx>, // |(_, v)| v
+) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ if let ExprKind::Closure(c) = m_arg.kind;
+ if let Body {params: [p], value: body_expr, generator_kind: _ } = cx.tcx.hir().body(c.body);
+ if let PatKind::Tuple([key_pat, val_pat], _) = p.pat.kind;
+
+ let (replacement_kind, binded_ident) = match (&key_pat.kind, &val_pat.kind) {
+ (key, PatKind::Binding(_, _, value, _)) if pat_is_wild(cx, key, m_arg) => ("value", value),
+ (PatKind::Binding(_, _, key, _), value) if pat_is_wild(cx, value, m_arg) => ("key", key),
+ _ => return,
+ };
+
+ let ty = cx.typeck_results().expr_ty(recv);
+ if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap);
+
+ then {
+ let mut applicability = rustc_errors::Applicability::MachineApplicable;
+ let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability);
+ let into_prefix = if map_type == "into_iter" {"into_"} else {""};
+
+ if_chain! {
+ if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind;
+ if let [local_ident] = path.segments;
+ if local_ident.ident.as_str() == binded_ident.as_str();
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ITER_KV_MAP,
+ expr.span,
- format!("{}.{}{}s()", recv_snippet, into_prefix, replacement_kind),
++ &format!("iterating on a map's {replacement_kind}s"),
+ "try",
- &format!("iterating on a map's {}s", replacement_kind),
++ format!("{recv_snippet}.{into_prefix}{replacement_kind}s()"),
+ applicability,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ ITER_KV_MAP,
+ expr.span,
- format!("{}.{}{}s().map(|{}| {})", recv_snippet, into_prefix, replacement_kind, binded_ident,
++ &format!("iterating on a map's {replacement_kind}s"),
+ "try",
++ format!("{recv_snippet}.{into_prefix}{replacement_kind}s().map(|{binded_ident}| {})",
+ snippet_with_applicability(cx, body_expr.span, "/* body */", &mut applicability)),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Returns `true` if the pattern is a `PatWild`, or is an ident prefixed with `_`
+/// that is not locally used.
+fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
+ match *pat {
+ PatKind::Wild => true,
+ PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
+ _ => false,
+ }
+}
--- /dev/null
- format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx)
+use super::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_parent_expr, higher};
+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;
+use rustc_span::symbol::sym;
+
+use super::ITER_NEXT_SLICE;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, caller_expr: &'tcx hir::Expr<'_>) {
+ // Skip lint if the `iter().next()` expression is a for loop argument,
+ // since it is already covered by `&loops::ITER_NEXT_LOOP`
+ let mut parent_expr_opt = get_parent_expr(cx, expr);
+ while let Some(parent_expr) = parent_expr_opt {
+ if higher::ForLoop::hir(parent_expr).is_some() {
+ return;
+ }
+ parent_expr_opt = get_parent_expr(cx, parent_expr);
+ }
+
+ if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() {
+ // caller is a Slice
+ if_chain! {
+ if let hir::ExprKind::Index(caller_var, index_expr) = &caller_expr.kind;
+ if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen })
+ = higher::Range::hir(index_expr);
+ if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind;
+ if let ast::LitKind::Int(start_idx, _) = start_lit.node;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let suggest = if start_idx == 0 {
+ format!("{}.first()", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability))
+ } else {
++ format!("{}.get({start_idx})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability))
+ };
+ span_lint_and_sugg(
+ cx,
+ ITER_NEXT_SLICE,
+ expr.span,
+ "using `.iter().next()` on a Slice without end index",
+ "try calling",
+ suggest,
+ applicability,
+ );
+ }
+ }
+ } else if is_vec_or_array(cx, caller_expr) {
+ // caller is a Vec or an Array
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ ITER_NEXT_SLICE,
+ expr.span,
+ "using `.iter().next()` on an array",
+ "try calling",
+ format!(
+ "{}.first()",
+ snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
+
+fn is_vec_or_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec)
+ || matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
+}
--- /dev/null
- &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type),
+use super::utils::derefs_to_slice;
+use crate::methods::iter_nth_zero;
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::ITER_NTH;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ iter_recv: &'tcx hir::Expr<'tcx>,
+ nth_recv: &hir::Expr<'_>,
+ nth_arg: &hir::Expr<'_>,
+ is_mut: bool,
+) {
+ let mut_str = if is_mut { "_mut" } else { "" };
+ let caller_type = if derefs_to_slice(cx, iter_recv, cx.typeck_results().expr_ty(iter_recv)).is_some() {
+ "slice"
+ } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::Vec) {
+ "Vec"
+ } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::VecDeque) {
+ "VecDeque"
+ } else {
+ iter_nth_zero::check(cx, expr, nth_recv, nth_arg);
+ return; // caller is not a type that we want to lint
+ };
+
+ span_lint_and_help(
+ cx,
+ ITER_NTH,
+ expr.span,
- &format!("calling `.get{}()` is both faster and more readable", mut_str),
++ &format!("called `.iter{mut_str}().nth()` on a {caller_type}"),
+ None,
++ &format!("calling `.get{mut_str}()` is both faster and more readable"),
+ );
+}
--- /dev/null
- use clippy_utils::{get_expr_use_or_unification_node, is_lang_ctor, is_no_std_crate};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
- let item = match &recv.kind {
- ExprKind::Array(v) if v.len() <= 1 => v.first(),
- ExprKind::Path(p) => {
- if is_lang_ctor(cx, p, OptionNone) {
- None
- } else {
- return;
- }
- },
- ExprKind::Call(f, some_args) if some_args.len() == 1 => {
- if let ExprKind::Path(p) = &f.kind {
- if is_lang_ctor(cx, p, OptionSome) {
- Some(&some_args[0])
- } else {
- return;
- }
- } else {
- return;
- }
- },
++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<'tcx>(cx: &LateContext<'tcx>, 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
- &format!("`drain(..)` used on a `{}`", ty_name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::Range;
+use clippy_utils::is_integer_const;
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+use super::ITER_WITH_DRAIN;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
+ if !matches!(recv.kind, ExprKind::Field(..))
+ && let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
+ && let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
+ && matches!(ty_name, sym::Vec | sym::VecDeque)
+ && let Some(range) = Range::hir(arg)
+ && is_full_range(cx, recv, range)
+ {
+ span_lint_and_sugg(
+ cx,
+ ITER_WITH_DRAIN,
+ span.with_hi(expr.span.hi()),
++ &format!("`drain(..)` used on a `{ty_name}`"),
+ "try this",
+ "into_iter()".to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ };
+}
+
+fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
+ range.start.map_or(true, |e| is_integer_const(cx, e, 0))
+ && range.end.map_or(true, |e| {
+ if range.limits == RangeLimits::HalfOpen
+ && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
+ && let ExprKind::MethodCall(name, self_arg, [], _) = e.kind
+ && name.ident.name == sym::len
+ && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
+ {
+ container_path.res == path.res
+ } else {
+ false
+ }
+ })
+}
--- /dev/null
- use clippy_utils::{is_lang_ctor, path_to_local_id};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
- use rustc_hir::{Closure, Expr, ExprKind, PatKind};
++use clippy_utils::{is_res_lang_ctor, path_res, path_to_local_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{ResultErr, ResultOk};
- if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, [err_arg]) = or_expr.kind;
- if is_lang_ctor(cx, err_path, ResultErr);
++use rustc_hir::{Expr, ExprKind, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::MANUAL_OK_OR;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ recv: &'tcx Expr<'_>,
+ or_expr: &'tcx Expr<'_>,
+ map_expr: &'tcx Expr<'_>,
+) {
+ if_chain! {
+ if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(method_id);
+ if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Option);
- "{}.ok_or({})",
- recv_snippet,
- reindented_err_arg_snippet
++ if let ExprKind::Call(err_path, [err_arg]) = or_expr.kind;
++ if is_res_lang_ctor(cx, path_res(cx, err_path), ResultErr);
+ if is_ok_wrapping(cx, map_expr);
+ if let Some(recv_snippet) = snippet_opt(cx, recv.span);
+ if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span);
+ if let Some(indent) = indent_of(cx, expr.span);
+ then {
+ let reindented_err_arg_snippet = reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4));
+ span_lint_and_sugg(
+ cx,
+ MANUAL_OK_OR,
+ expr.span,
+ "this pattern reimplements `Option::ok_or`",
+ "replace with",
+ format!(
- if let ExprKind::Path(ref qpath) = map_expr.kind {
- if is_lang_ctor(cx, qpath, ResultOk) {
- return true;
- }
- }
- if_chain! {
- if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind;
- let body = cx.tcx.hir().body(body);
- if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
- if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
- if is_lang_ctor(cx, ok_path, ResultOk);
- then { path_to_local_id(ok_arg, param_id) } else { false }
++ "{recv_snippet}.ok_or({reindented_err_arg_snippet})"
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
++ match map_expr.kind {
++ ExprKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, map_expr.hir_id), ResultOk) => true,
++ ExprKind::Closure(closure) => {
++ let body = cx.tcx.hir().body(closure.body);
++ if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind
++ && let ExprKind::Call(callee, [ok_arg]) = body.value.kind
++ && is_res_lang_ctor(cx, path_res(cx, callee), ResultOk)
++ {
++ path_to_local_id(ok_arg, param_id)
++ } else {
++ false
++ }
++ },
++ _ => false,
+ }
+}
--- /dev/null
- &format!("try using `saturating_{}`", arith),
+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 mm = if let Some(mm) = is_min_or_max(cx, unwrap_arg) {
+ mm
+ } else {
+ return;
+ };
+
+ if ty.is_signed() {
+ use self::{
+ MinMax::{Max, Min},
+ Sign::{Neg, Pos},
+ };
+
+ let sign = if let Some(sign) = lit_sign(arith_rhs) {
+ sign
+ } 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",
- "{}.saturating_{}({})",
++ &format!("try using `saturating_{arith}`"),
+ format!(
- arith,
++ "{}.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<'tcx>(cx: &LateContext<'tcx>, 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
- use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item, match_type};
- use clippy_utils::{is_expr_path_def_path, paths};
+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;
- || (match_type(cx, ty, &paths::COW) && get_ty_param(ty).map_or(false, Ty::is_str))
++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_diagnostic_item(cx, ty, sym::String)
+ || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, Ty::is_str))
- if is_expr_path_def_path(cx, repeat_fn, &paths::ITER_REPEAT);
++ || (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_diagnostic_item(cx, ty, sym::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;
- format!("{}.repeat({})", val_str, count_snip),
++ if is_path_diagnostic_item(cx, repeat_fn, sym::iter_repeat);
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(collect_expr), sym::String);
+ if let Some(collect_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_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(collect_id) == Some(iter_trait_id);
+ 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
- &format!("consider calling the dedicated `{}` method", sugg_method),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
+use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs, peel_blocks};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Span};
+
+use super::MAP_CLONE;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'_>,
+ e: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) {
+ if_chain! {
+ if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id);
+ if cx.tcx.impl_of_method(method_id)
+ .map_or(false, |id| is_type_diagnostic_item(cx, cx.tcx.type_of(id), sym::Option))
+ || is_diag_trait_item(cx, method_id, sym::Iterator);
+ if let hir::ExprKind::Closure(&hir::Closure{ body, .. }) = arg.kind;
+ then {
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(closure_body.value);
+ match closure_body.params[0].pat.kind {
+ hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding(
+ hir::BindingAnnotation::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,
++ "{}.{sugg_method}()",
+ snippet_with_applicability(cx, root, "..", &mut applicability),
+ ),
+ applicability,
+ );
+}
--- /dev/null
- &format!("called `map(..).flatten()` on `{}`", caller_ty_name),
- &format!(
- "try replacing `map` with `{}` and remove the `.flatten()`",
- method_to_use
- ),
- format!("{}({})", method_to_use, closure_snippet),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::{symbol::sym, Span};
+
+use super::MAP_FLATTEN;
+
+/// lint use of `map().flatten()` for `Iterators` and 'Options'
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
+ if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ MAP_FLATTEN,
+ expr.span.with_lo(map_span.lo()),
++ &format!("called `map(..).flatten()` on `{caller_ty_name}`"),
++ &format!("try replacing `map` with `{method_to_use}` and remove the `.flatten()`"),
++ format!("{method_to_use}({closure_snippet})"),
+ applicability,
+ );
+ }
+}
+
+fn try_get_caller_ty_name_and_method_name(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ caller_expr: &Expr<'_>,
+ map_arg: &Expr<'_>,
+) -> Option<(&'static str, &'static str)> {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ if is_map_to_option(cx, map_arg) {
+ // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
+ Some(("Iterator", "filter_map"))
+ } else {
+ // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
+ Some(("Iterator", "flat_map"))
+ }
+ } else {
+ if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() {
+ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) {
+ return Some(("Option", "and_then"));
+ } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+ return Some(("Result", "and_then"));
+ }
+ }
+ None
+ }
+}
+
+fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool {
+ let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
+ match map_closure_ty.kind() {
+ ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
+ let map_closure_sig = match map_closure_ty.kind() {
+ ty::Closure(_, substs) => substs.as_closure().sig(),
+ _ => map_closure_ty.fn_sig(cx.tcx),
+ };
+ let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
+ is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("remove the call to `{}`", name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_expr_identity_function, is_trait_method};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::MAP_IDENTITY;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ caller: &hir::Expr<'_>,
+ map_arg: &hir::Expr<'_>,
+ name: &str,
+ _map_span: Span,
+) {
+ let caller_ty = cx.typeck_results().expr_ty(caller);
+
+ if_chain! {
+ if is_trait_method(cx, expr, sym::Iterator)
+ || is_type_diagnostic_item(cx, caller_ty, sym::Result)
+ || is_type_diagnostic_item(cx, caller_ty, sym::Option);
+ if is_expr_identity_function(cx, map_arg);
+ if let Some(sugg_span) = expr.span.trim_start(caller.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ MAP_IDENTITY,
+ sugg_span,
+ "unnecessary map of the identity function",
++ &format!("remove the call to `{name}`"),
+ String::new(),
+ Applicability::MachineApplicable,
+ )
+ }
+ }
+}
--- /dev/null
- format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_semver::RustcVersion;
+use rustc_span::symbol::sym;
+
+use super::MAP_UNWRAP_OR;
+
+/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
+/// Return true if lint triggered
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ map_arg: &'tcx hir::Expr<'_>,
+ unwrap_arg: &'tcx hir::Expr<'_>,
+ msrv: Option<RustcVersion>,
+) -> bool {
+ // lint if the caller of `map()` is an `Option`
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
+ if is_result && !meets_msrv(msrv, msrvs::RESULT_MAP_OR_ELSE) {
+ return false;
+ }
+
+ if is_option || is_result {
+ // Don't make a suggestion that may fail to compile due to mutably borrowing
+ // the same variable twice.
+ let map_mutated_vars = mutated_variables(recv, cx);
+ let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
+ if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
+ if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // lint message
+ let msg = if is_option {
+ "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling \
+ `map_or_else(<g>, <f>)` instead"
+ } else {
+ "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value. This can be done more directly by calling \
+ `.map_or_else(<g>, <f>)` instead"
+ };
+ // get snippets for args to map() and unwrap_or_else()
+ let map_snippet = snippet(cx, map_arg.span, "..");
+ let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
+ // lint, with note if neither arg is > 1 line and both map() and
+ // unwrap_or_else() have the same span
+ let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
+ let same_span = map_arg.span.ctxt() == unwrap_arg.span.ctxt();
+ if same_span && !multiline {
+ let var_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ MAP_UNWRAP_OR,
+ expr.span,
+ msg,
+ "try this",
++ format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ } else if same_span && multiline {
+ span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
+ return true;
+ }
+ }
+
+ false
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_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_option_as_deref;
+mod needless_option_take;
+mod no_effect_replace;
+mod obfuscated_if_else;
+mod ok_expect;
+mod open_options;
+mod option_as_ref_deref;
+mod option_map_or_none;
+mod option_map_unwrap_or;
+mod or_fun_call;
+mod or_then_unwrap;
+mod path_buf_push_overwrite;
+mod range_zip_with_len;
+mod repeat_once;
+mod search_is_some;
+mod single_char_add_str;
+mod single_char_insert_string;
+mod single_char_pattern;
+mod single_char_push_string;
+mod skip_while_next;
+mod stable_sort_primitive;
+mod str_splitn;
+mod string_extend_chars;
+mod suspicious_map;
+mod suspicious_splitn;
+mod suspicious_to_owned;
+mod uninit_assumed_init;
+mod unit_hash;
+mod unnecessary_filter_map;
+mod unnecessary_fold;
+mod unnecessary_iter_cloned;
+mod unnecessary_join;
+mod unnecessary_lazy_eval;
+mod unnecessary_sort_by;
+mod unnecessary_to_owned;
+mod unwrap_or_else_default;
+mod unwrap_used;
+mod useless_asref;
+mod utils;
+mod vec_resize_to_zero;
+mod verbose_file_reads;
+mod wrong_self_convention;
+mod zst_offset;
+
+use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::ty::{contains_adt_constructor, implements_trait, is_copy, is_type_diagnostic_item};
+use clippy_utils::{
+ contains_return, get_trait_def_id, is_trait_method, iter_input_pats, meets_msrv, msrvs, paths, return_ty,
+};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, PrimTy, QPath, 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};
- if_chain! {
- if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
- if let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next();
-
- let method_sig = cx.tcx.fn_sig(impl_item.def_id.def_id);
+
+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
+ /// let hello = "hesuo worpd".replace(&['s', 'u', 'p'], "l");
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub COLLAPSIBLE_STR_REPLACE,
+ perf,
+ "collapse consecutive calls to str::replace (2 or more) into a single call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.cloned().<func>()` where call to `.cloned()` can be postponed.
+ ///
+ /// ### 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?
+ /// The function will always be called and potentially
+ /// allocate an object acting as the default.
+ ///
+ /// ### Known problems
+ /// If the function has side-effects, not calling it will
+ /// change the semantic of the program, but you shouldn't rely on that anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or(String::new());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_else(String::new);
+ ///
+ /// // or
+ ///
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_default();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OR_FUN_CALL,
+ perf,
+ "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
+ /// Checks for usage of `_.as_ref().map(Deref::deref)` or it's 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);
+ ///
+ /// let data = cow.into_owned();
+ /// assert!(matches!(data, String))
+ /// ```
+ #[clippy::version = "1.65.0"]
+ pub SUSPICIOUS_TO_OWNED,
+ suspicious,
+ "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to [`splitn`]
+ /// (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"]
+ pub ITER_ON_SINGLE_ITEMS,
+ nursery,
+ "Iterator for array of length 1"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections
+ ///
+ /// ### Why is this bad?
+ ///
+ /// It is simpler to use the empty function from the standard library:
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// use std::{slice, option};
+ /// let a: slice::Iter<i32> = [].iter();
+ /// let f: option::IntoIter<i32> = None.into_iter();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::iter;
+ /// let a: iter::Empty<i32> = iter::empty();
+ /// let b: iter::Empty<i32> = iter::empty();
+ /// ```
+ ///
+ /// ### Known problems
+ ///
+ /// The type of the resulting iterator might become incompatible with its usage
+ #[clippy::version = "1.64.0"]
+ pub ITER_ON_EMPTY_COLLECTIONS,
+ nursery,
+ "Iterator for empty array"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for naive byte counts
+ ///
+ /// ### Why is this bad?
+ /// The [`bytecount`](https://crates.io/crates/bytecount)
+ /// crate has methods to count your bytes faster, especially for large slices.
+ ///
+ /// ### Known problems
+ /// If you have predominantly small slices, the
+ /// `bytecount::count(..)` method may actually be slower. However, if you can
+ /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be
+ /// faster in those cases.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1_u8];
+ /// let count = vec.iter().filter(|x| **x == 0u8).count();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// # let vec = vec![1_u8];
+ /// let count = bytecount::count(&vec, 0u8);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NAIVE_BYTECOUNT,
+ pedantic,
+ "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It checks for `str::bytes().count()` and suggests replacing it with
+ /// `str::len()`.
+ ///
+ /// ### Why is this bad?
+ /// `str::bytes().count()` is longer and may not be as performant as using
+ /// `str::len()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// "hello".bytes().count();
+ /// String::from("hello").bytes().count();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// "hello".len();
+ /// String::from("hello").len();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub BYTES_COUNT_TO_LEN,
+ complexity,
+ "Using `bytes().count()` when `len()` performs the same functionality"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `ends_with` with possible file extensions
+ /// and suggests to use a case-insensitive approach instead.
+ ///
+ /// ### Why is this bad?
+ /// `ends_with` is case-sensitive and may not detect files with a valid extension.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn is_rust_file(filename: &str) -> bool {
+ /// filename.ends_with(".rs")
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn is_rust_file(filename: &str) -> bool {
+ /// let filename = std::path::Path::new(filename);
+ /// filename.extension()
+ /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs"))
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ pedantic,
+ "Checks for calls to ends_with with case-sensitive file extensions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `x.get(0)` instead of
+ /// `x.first()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `x.first()` is easier to read and has the same
+ /// result.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let first_element = x.get(0);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![2, 3, 5];
+ /// let first_element = x.first();
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub GET_FIRST,
+ style,
+ "Using `x.get(0)` when `x.first()` is simpler"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Finds patterns that reimplement `Option::ok_or`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Concise code helps focusing on behavior instead of boilerplate.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.map_or(Err("error"), |v| Ok(v));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.ok_or("error");
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MANUAL_OK_OR,
+ pedantic,
+ "finds patterns that can be encoded more concisely with `Option::ok_or`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `map(|x| x.clone())` or
+ /// dereferencing closures for `Copy` types, on `Iterator` or `Option`,
+ /// and suggests `cloned()` or `copied()` instead
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.map(|i| *i);
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.cloned();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MAP_CLONE,
+ style,
+ "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `map_err(|_| Some::Enum)`
+ ///
+ /// ### Why is this bad?
+ /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error
+ ///
+ /// ### Example
+ /// Before:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// #[derive(Debug)]
+ /// enum Error {
+ /// Indivisible,
+ /// Remainder(u8),
+ /// }
+ ///
+ /// impl fmt::Display for Error {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// match self {
+ /// Error::Indivisible => write!(f, "could not divide input by three"),
+ /// Error::Remainder(remainder) => write!(
+ /// f,
+ /// "input is not divisible by three, remainder = {}",
+ /// remainder
+ /// ),
+ /// }
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for Error {}
+ ///
+ /// fn divisible_by_3(input: &str) -> Result<(), Error> {
+ /// input
+ /// .parse::<i32>()
+ /// .map_err(|_| Error::Indivisible)
+ /// .map(|v| v % 3)
+ /// .and_then(|remainder| {
+ /// if remainder == 0 {
+ /// Ok(())
+ /// } else {
+ /// Err(Error::Remainder(remainder as u8))
+ /// }
+ /// })
+ /// }
+ /// ```
+ ///
+ /// After:
+ /// ```rust
+ /// use std::{fmt, num::ParseIntError};
+ ///
+ /// #[derive(Debug)]
+ /// enum Error {
+ /// Indivisible(ParseIntError),
+ /// Remainder(u8),
+ /// }
+ ///
+ /// impl fmt::Display for Error {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// match self {
+ /// Error::Indivisible(_) => write!(f, "could not divide input by three"),
+ /// Error::Remainder(remainder) => write!(
+ /// f,
+ /// "input is not divisible by three, remainder = {}",
+ /// remainder
+ /// ),
+ /// }
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for Error {
+ /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ /// match self {
+ /// Error::Indivisible(source) => Some(source),
+ /// _ => None,
+ /// }
+ /// }
+ /// }
+ ///
+ /// fn divisible_by_3(input: &str) -> Result<(), Error> {
+ /// input
+ /// .parse::<i32>()
+ /// .map_err(Error::Indivisible)
+ /// .map(|v| v % 3)
+ /// .and_then(|remainder| {
+ /// if remainder == 0 {
+ /// Ok(())
+ /// } else {
+ /// Err(Error::Remainder(remainder as u8))
+ /// }
+ /// })
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub MAP_ERR_IGNORE,
+ restriction,
+ "`map_err` should not ignore the original error"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `&mut Mutex::lock` calls
+ ///
+ /// ### Why is this bad?
+ /// `Mutex::lock` is less efficient than
+ /// calling `Mutex::get_mut`. In addition you also have a statically
+ /// guarantee that the mutex isn't locked, instead of just a runtime
+ /// guarantee.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::{Arc, Mutex};
+ ///
+ /// let mut value_rc = Arc::new(Mutex::new(42_u8));
+ /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap();
+ ///
+ /// let mut value = value_mutex.lock().unwrap();
+ /// *value += 1;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::sync::{Arc, Mutex};
+ ///
+ /// let mut value_rc = Arc::new(Mutex::new(42_u8));
+ /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap();
+ ///
+ /// let value = value_mutex.get_mut().unwrap();
+ /// *value += 1;
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MUT_MUTEX_LOCK,
+ style,
+ "`&mut Mutex::lock` does unnecessary locking"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for duplicate open options as well as combinations
+ /// that make no sense.
+ ///
+ /// ### Why is this bad?
+ /// In the best case, the code will be harder to read than
+ /// necessary. I don't know the worst case.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::fs::OpenOptions;
+ ///
+ /// OpenOptions::new().read(true).truncate(true);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NONSENSICAL_OPEN_OPTIONS,
+ correctness,
+ "nonsensical combination of options for opening a file"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
+ /// calls on `PathBuf` that can cause overwrites.
+ ///
+ /// ### Why is this bad?
+ /// Calling `push` with a root path at the start can overwrite the
+ /// previous defined path.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::path::PathBuf;
+ ///
+ /// let mut x = PathBuf::from("/foo");
+ /// x.push("/bar");
+ /// assert_eq!(x, PathBuf::from("/bar"));
+ /// ```
+ /// Could be written:
+ ///
+ /// ```rust
+ /// use std::path::PathBuf;
+ ///
+ /// let mut x = PathBuf::from("/foo");
+ /// x.push("bar");
+ /// assert_eq!(x, PathBuf::from("/foo/bar"));
+ /// ```
+ #[clippy::version = "1.36.0"]
+ pub PATH_BUF_PUSH_OVERWRITE,
+ nursery,
+ "calling `push` with file system root on `PathBuf` can overwrite it"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for zipping a collection with the range of
+ /// `0.._.len()`.
+ ///
+ /// ### Why is this bad?
+ /// The code is better expressed with `.enumerate()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = vec![1];
+ /// let _ = x.iter().zip(0..x.len());
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = vec![1];
+ /// let _ = x.iter().enumerate();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_ZIP_WITH_LEN,
+ complexity,
+ "zipping iterator with a range when `enumerate()` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.repeat(1)` and suggest the following method for each types.
+ /// - `.to_string()` for `str`
+ /// - `.clone()` for `String`
+ /// - `.to_vec()` for `slice`
+ ///
+ /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if
+ /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306))
+ ///
+ /// ### Why is this bad?
+ /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning
+ /// the string is the intention behind this, `clone()` should be used.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// let x = String::from("hello world").repeat(1);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// let x = String::from("hello world").clone();
+ /// }
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub REPEAT_ONCE,
+ complexity,
+ "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` "
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// When sorting primitive values (integers, bools, chars, as well
+ /// as arrays, slices, and tuples of such items), it is typically better to
+ /// use an unstable sort than a stable sort.
+ ///
+ /// ### Why is this bad?
+ /// Typically, using a stable sort consumes more memory and cpu cycles.
+ /// Because values which compare equal are identical, preserving their
+ /// relative order (the guarantee that a stable sort provides) means
+ /// nothing, while the extra costs still apply.
+ ///
+ /// ### Known problems
+ ///
+ /// As pointed out in
+ /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241),
+ /// a stable sort can instead be significantly faster for certain scenarios
+ /// (eg. when a sorted vector is extended with new data and resorted).
+ ///
+ /// For more information and benchmarking results, please refer to the
+ /// issue linked above.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut vec = vec![2, 1, 3];
+ /// vec.sort();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let mut vec = vec![2, 1, 3];
+ /// vec.sort_unstable();
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub STABLE_SORT_PRIMITIVE,
+ pedantic,
+ "use of sort() when sort_unstable() is equivalent"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `().hash(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::hash::Hash;
+ /// # use std::collections::hash_map::DefaultHasher;
+ /// # enum Foo { Empty, WithValue(u8) }
+ /// # use Foo::*;
+ /// # let mut state = DefaultHasher::new();
+ /// # let my_enum = Foo::Empty;
+ /// match my_enum {
+ /// Empty => ().hash(&mut state),
+ /// WithValue(x) => x.hash(&mut state),
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::hash::Hash;
+ /// # use std::collections::hash_map::DefaultHasher;
+ /// # enum Foo { Empty, WithValue(u8) }
+ /// # use Foo::*;
+ /// # let mut state = DefaultHasher::new();
+ /// # let my_enum = Foo::Empty;
+ /// match my_enum {
+ /// Empty => 0_u8.hash(&mut state),
+ /// WithValue(x) => x.hash(&mut state),
+ /// }
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNIT_HASH,
+ correctness,
+ "hashing a unit value, which does nothing"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects uses of `Vec::sort_by` passing in a closure
+ /// which compares the two arguments, either directly or indirectly.
+ ///
+ /// ### Why is this bad?
+ /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if
+ /// possible) than to use `Vec::sort_by` and a more complicated
+ /// closure.
+ ///
+ /// ### Known problems
+ /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already
+ /// imported by a use statement, then it will need to be added manually.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct A;
+ /// # impl A { fn foo(&self) {} }
+ /// # let mut vec: Vec<A> = Vec::new();
+ /// vec.sort_by(|a, b| a.foo().cmp(&b.foo()));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # struct A;
+ /// # impl A { fn foo(&self) {} }
+ /// # let mut vec: Vec<A> = Vec::new();
+ /// vec.sort_by_key(|a| a.foo());
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub UNNECESSARY_SORT_BY,
+ complexity,
+ "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds occurrences of `Vec::resize(0, an_int)`
+ ///
+ /// ### Why is this bad?
+ /// This is probably an argument inversion mistake.
+ ///
+ /// ### Example
+ /// ```rust
+ /// vec!(1, 2, 3, 4, 5).resize(0, 5)
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// vec!(1, 2, 3, 4, 5).clear()
+ /// ```
+ #[clippy::version = "1.46.0"]
+ pub VEC_RESIZE_TO_ZERO,
+ correctness,
+ "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of File::read_to_end and File::read_to_string.
+ ///
+ /// ### Why is this bad?
+ /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values.
+ /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html)
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # use std::io::Read;
+ /// # use std::fs::File;
+ /// let mut f = File::open("foo.txt").unwrap();
+ /// let mut bytes = Vec::new();
+ /// f.read_to_end(&mut bytes).unwrap();
+ /// ```
+ /// Can be written more concisely as
+ /// ```rust,no_run
+ /// # use std::fs;
+ /// let mut bytes = fs::read("foo.txt").unwrap();
+ /// ```
+ #[clippy::version = "1.44.0"]
+ pub VERBOSE_FILE_READS,
+ restriction,
+ "use of `File::read_to_end` or `File::read_to_string`"
+}
+
+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"
+}
+
+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,
+]);
+
+/// Extracts a method call name, args, and `Span` of the method name.
+fn method_call<'tcx>(
+ recv: &'tcx hir::Expr<'tcx>,
+) -> Option<(&'tcx str, &'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>], Span)> {
+ if let ExprKind::MethodCall(path, receiver, args, _) = recv.kind {
+ if !args.iter().any(|e| e.span.from_expansion()) && !receiver.span.from_expansion() {
+ let name = path.ident.name.as_str();
+ return Some((name, receiver, args, path.ident.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.def_id);
+
+ let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
-
- let first_arg_ty = method_sig.inputs().iter().next();
-
- // check conventions w.r.t. conversion method names and predicates
- if let Some(first_arg_ty) = first_arg_ty;
-
- then {
- // if this impl block implements a trait, lint in trait definition instead
- if !implements_trait && cx.access_levels.is_exported(impl_item.def_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) &&
- 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 let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind {
++ let method_sig = cx.tcx.fn_sig(impl_item.def_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
- if sig.decl.implicit_self.has_implicit_self()
++ 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.access_levels.is_exported(impl_item.def_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
++ ),
++ );
+ }
+ }
++ }
+
- && cx.access_levels.is_exported(impl_item.def_id.def_id))
++ if sig.decl.implicit_self.has_implicit_self()
+ && !(self.avoid_breaking_exported_api
- *first_arg_ty,
++ && cx.access_levels.is_exported(impl_item.def_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
+ );
+ }
- // FIXME: default doesn't work
+ }
+
+ // 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());
+
+ // 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;
+ }
+
+ // 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 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.def_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.def_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<'_>) {
+ if let Some((name, recv, args, 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),
+ ("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);
+ }
+ },
+ _ => {},
+ },
+ ("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) {
+ Some(("cloned", recv2, [], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false),
+ Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _)) => {
+ iter_count::check(cx, expr, recv2, name2);
+ },
+ Some(("map", _, [arg], _)) => suspicious_map::check(cx, expr, recv, arg),
+ Some(("filter", recv2, [arg], _)) => bytecount::check(cx, expr, recv2, arg),
+ Some(("bytes", recv2, [], _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
+ _ => {},
+ },
+ ("drain", [arg]) => {
+ iter_with_drain::check(cx, expr, recv, span, arg);
+ },
+ ("ends_with", [arg]) => {
+ if let ExprKind::MethodCall(.., span) = expr.kind {
+ case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
+ }
+ },
+ ("expect", [_]) => match method_call(recv) {
+ Some(("ok", recv, [], _)) => ok_expect::check(cx, expr, recv),
+ Some(("err", recv, [], err_span)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
+ _ => expect_used::check(cx, expr, recv, 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) {
+ 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(("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(("collect", _, _, span)) = method_call(recv) {
+ unnecessary_join::check(cx, expr, recv, join_arg, span);
+ }
+ },
+ ("last", []) | ("skip", [_]) => {
+ 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((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((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", []) => {
+ 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) {
+ 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);
+ },
+ ("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]) => {
+ 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", recv, [get_arg], _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, false);
+ },
+ Some(("get_mut", recv, [get_arg], _)) => {
+ get_unwrap::check(cx, expr, recv, get_arg, true);
+ },
+ Some(("or", recv, [or_arg], or_span)) => {
+ or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
+ },
+ _ => {},
+ }
+ unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests);
+ },
+ ("unwrap_err", []) => unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests),
+ ("unwrap_or", [u_arg]) => match method_call(recv) {
+ Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _)) => {
+ manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
+ },
+ Some(("map", m_recv, [m_arg], span)) => {
+ option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
+ },
+ Some(("then_some", t_recv, [t_arg], _)) => {
+ obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
+ },
+ _ => {},
+ },
+ ("unwrap_or_else", [u_arg]) => match method_call(recv) {
+ Some(("map", recv, [map_arg], _))
+ if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
+ _ => {
+ unwrap_or_else_default::check(cx, expr, recv, u_arg);
+ unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
+ },
+ },
+ ("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) {
+ 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),
- No,
+ 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_path = match mutability {
+ hir::Mutability::Not => &paths::ASREF_TRAIT,
+ hir::Mutability::Mut => &paths::ASMUT_TRAIT,
+ };
+
+ let trait_def_id = match get_trait_def_id(cx, trait_path) {
+ Some(did) => did,
+ None => 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 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
+ }
+}
+
+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
- let hint = format!("{}.{}()", snippet(cx, as_ref_recv.span, ".."), method_hint);
- let suggestion = format!("try using {} instead", method_hint);
+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<'tcx>(
+ cx: &LateContext<'tcx>,
+ 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]; 9] = [
+ &paths::DEREF_TRAIT_METHOD,
+ &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| {
+ 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();
+ 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" };
- "called `{0}` on an Option value. This can be done more directly \
- by calling `{1}` instead",
- current_method, hint
++ 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
- use clippy_utils::{is_lang_ctor, path_def_id};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
- let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
- is_lang_ctor(cx, qpath, OptionNone)
- } else {
- return;
- };
-
- if !default_arg_is_none {
++use clippy_utils::{is_res_lang_ctor, path_def_id, path_res};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_lint::LateContext;
+use rustc_middle::ty::DefIdTree;
+use rustc_span::symbol::sym;
+
+use super::OPTION_MAP_OR_NONE;
+use super::RESULT_MAP_OR_INTO_OPTION;
+
+// The expression inside a closure may or may not have surrounding braces
+// which causes problems when generating a suggestion.
+fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
+ match expr.kind {
+ hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
+ hir::ExprKind::Block(block, _) => {
+ match (block.stmts, block.expr) {
+ (&[], Some(inner_expr)) => {
+ // If block only contains an expression,
+ // reduce `|x| { x + 1 }` to `|x| x + 1`
+ reduce_unit_expression(inner_expr)
+ },
+ _ => None,
+ }
+ },
+ _ => None,
+ }
+}
+
+/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ def_arg: &'tcx hir::Expr<'_>,
+ map_arg: &'tcx hir::Expr<'_>,
+) {
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
+ // There are two variants of this `map_or` lint:
+ // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
+ // (2) using `map_or` as a combinator instead of `and_then`
+ //
+ // (For this lint) we don't care if any other type calls `map_or`
+ if !is_option && !is_result {
+ return;
+ }
+
- let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
- is_lang_ctor(cx, qpath, OptionSome)
- } else {
- false
- };
++ if !is_res_lang_ctor(cx, path_res(cx, def_arg), OptionNone) {
+ // nothing to lint!
+ return;
+ }
+
- format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet),
++ let f_arg_is_some = is_res_lang_ctor(cx, path_res(cx, map_arg), OptionSome);
+
+ if is_option {
+ let self_snippet = snippet(cx, recv.span, "..");
+ if_chain! {
+ if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = map_arg.kind;
+ let arg_snippet = snippet(cx, fn_decl_span, "..");
+ let body = cx.tcx.hir().body(body);
+ if let Some((func, [arg_char])) = reduce_unit_expression(body.value);
+ if let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id));
+ if Some(id) == cx.tcx.lang_items().option_some_variant();
+ then {
+ let func_snippet = snippet(cx, arg_char.span, "..");
+ let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
+ `map(..)` instead";
+ return span_lint_and_sugg(
+ cx,
+ OPTION_MAP_OR_NONE,
+ expr.span,
+ msg,
+ "try using `map` instead",
- format!("{0}.and_then({1})", self_snippet, func_snippet),
++ format!("{self_snippet}.map({arg_snippet} {func_snippet})"),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ let func_snippet = snippet(cx, map_arg.span, "..");
+ let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
+ `and_then(..)` instead";
+ span_lint_and_sugg(
+ cx,
+ OPTION_MAP_OR_NONE,
+ expr.span,
+ msg,
+ "try using `and_then` instead",
- format!("{0}.ok()", self_snippet),
++ format!("{self_snippet}.and_then({func_snippet})"),
+ Applicability::MachineApplicable,
+ );
+ } else if f_arg_is_some {
+ let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
+ `ok()` instead";
+ let self_snippet = snippet(cx, recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ RESULT_MAP_OR_INTO_OPTION,
+ expr.span,
+ msg,
+ "try using `ok` instead",
++ format!("{self_snippet}.ok()"),
+ Applicability::MachineApplicable,
+ );
+ }
+}
--- /dev/null
- "called `map(<f>).unwrap_or({})` on an `Option` value. \
- This can be done more directly by calling `{}` instead",
- arg, suggest
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_copy;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_path, Visitor};
+use rustc_hir::{self, HirId, Path};
+use rustc_lint::LateContext;
+use rustc_middle::hir::nested_filter;
+use rustc_span::source_map::Span;
+use rustc_span::{sym, Symbol};
+
+use super::MAP_UNWRAP_OR;
+
+/// lint use of `map().unwrap_or()` for `Option`s
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &rustc_hir::Expr<'_>,
+ recv: &rustc_hir::Expr<'_>,
+ map_arg: &'tcx rustc_hir::Expr<'_>,
+ unwrap_recv: &rustc_hir::Expr<'_>,
+ unwrap_arg: &'tcx rustc_hir::Expr<'_>,
+ map_span: Span,
+) {
+ // lint if the caller of `map()` is an `Option`
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option) {
+ if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
+ // Do not lint if the `map` argument uses identifiers in the `map`
+ // argument that are also used in the `unwrap_or` argument
+
+ let mut unwrap_visitor = UnwrapVisitor {
+ cx,
+ identifiers: FxHashSet::default(),
+ };
+ unwrap_visitor.visit_expr(unwrap_arg);
+
+ let mut map_expr_visitor = MapExprVisitor {
+ cx,
+ identifiers: unwrap_visitor.identifiers,
+ found_identifier: false,
+ };
+ map_expr_visitor.visit_expr(map_arg);
+
+ if map_expr_visitor.found_identifier {
+ return;
+ }
+ }
+
+ if unwrap_arg.span.ctxt() != map_span.ctxt() {
+ return;
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ // get snippet for unwrap_or()
+ let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
+ // lint message
+ // comparing the snippet from source to raw text ("None") below is safe
+ // because we already have checked the type.
+ let arg = if unwrap_snippet == "None" { "None" } else { "<a>" };
+ let unwrap_snippet_none = unwrap_snippet == "None";
+ let suggest = if unwrap_snippet_none {
+ "and_then(<f>)"
+ } else {
+ "map_or(<a>, <f>)"
+ };
+ let msg = &format!(
- suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{}, ", unwrap_snippet)));
++ "called `map(<f>).unwrap_or({arg})` on an `Option` value. \
++ This can be done more directly by calling `{suggest}` instead"
+ );
+
+ span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
+ let map_arg_span = map_arg.span;
+
+ let mut suggestion = vec![
+ (
+ map_span,
+ String::from(if unwrap_snippet_none { "and_then" } else { "map_or" }),
+ ),
+ (expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
+ ];
+
+ if !unwrap_snippet_none {
- diag.multipart_suggestion(&format!("use `{}` instead", suggest), suggestion, applicability);
++ suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
+ }
+
++ diag.multipart_suggestion(&format!("use `{suggest}` instead"), suggestion, applicability);
+ });
+ }
+}
+
+struct UnwrapVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ identifiers: FxHashSet<Symbol>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
+ self.identifiers.insert(ident(path));
+ walk_path(self, path);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+struct MapExprVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ identifiers: FxHashSet<Symbol>,
+ found_identifier: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MapExprVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
+ if self.identifiers.contains(&ident(path)) {
+ self.found_identifier = true;
+ return;
+ }
+ walk_path(self, path);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+fn ident(path: &Path<'_>) -> Symbol {
+ path.segments
+ .last()
+ .expect("segments should be composed of at least 1 element")
+ .ident
+ .name
+}
--- /dev/null
- &format!("use of `{}` followed by a call to `{}`", name, path),
+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, match_type};
+use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
+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};
+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!("{}()", sugg),
++ &format!("use of `{name}` followed by a call to `{path}`"),
+ "try this",
- format!("|{}| {}", l_arg, snippet).into()
++ 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<'_>,
+ span: Span,
+ // None if lambda is required
+ fun_span: Option<Span>,
+ ) {
+ // (path, fn_has_argument, methods, suffix)
+ const KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [
+ (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
+ (&paths::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| match_type(cx, self_ty, i.0));
+
+ if poss.contains(&name);
+
+ then {
+ let macro_expanded_snipped;
+ let sugg: Cow<'_, str> = {
+ let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
+ (false, Some(fun_span)) => (fun_span, false),
+ _ => (arg.span, true),
+ };
+ 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, "..");
+ match macro_expanded_snipped.strip_prefix("$crate::vec::") {
+ Some(stripped) => Cow::from(stripped),
+ None => macro_expanded_snipped
+ }
+ }
+ else {
+ not_macro_argument_snippet
+ }
+ };
+
+ if use_lambda {
+ let l_arg = if fn_has_arguments { "_" } else { "" };
- &format!("use of `{}` followed by a function call", name),
++ format!("|{l_arg}| {snippet}").into()
+ } else {
+ snippet
+ }
+ };
+ let span_replace_word = method_span.with_hi(span.hi());
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span_replace_word,
- format!("{}_{}({})", name, suffix, sugg),
++ &format!("use of `{name}` followed by a function call"),
+ "try this",
++ format!("{name}_{suffix}({sugg})"),
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+
+ if let [arg] = args {
+ let inner_arg = if let hir::ExprKind::Block(
+ hir::Block {
+ stmts: [],
+ expr: Some(expr),
+ ..
+ },
+ _,
+ ) = arg.kind
+ {
+ expr
+ } else {
+ 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, fun_span);
+ }
+ },
+ hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
+ check_general_case(cx, name, method_span, receiver, arg, expr.span, None);
+ },
+ _ => (),
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
- && let ExprKind::Path(qpath) = &some_expr.kind
- && is_lang_ctor(cx, qpath, item)
++use clippy_utils::{diagnostics::span_lint_and_sugg, is_res_lang_ctor, path_res};
+use rustc_errors::Applicability;
+use rustc_hir::{lang_items::LangItem, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Span};
+
+use super::OR_THEN_UNWRAP;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ unwrap_expr: &Expr<'_>,
+ recv: &'tcx Expr<'tcx>,
+ or_arg: &'tcx Expr<'_>,
+ or_span: Span,
+) {
+ let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result)
+ let title;
+ let or_arg_content: Span;
+
+ if is_type_diagnostic_item(cx, ty, sym::Option) {
+ title = "found `.or(Some(…)).unwrap()`";
+ if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
+ or_arg_content = content;
+ } else {
+ return;
+ }
+ } else if is_type_diagnostic_item(cx, ty, sym::Result) {
+ title = "found `.or(Ok(…)).unwrap()`";
+ if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
+ or_arg_content = content;
+ } else {
+ return;
+ }
+ } else {
+ // Someone has implemented a struct with .or(...).unwrap() chaining,
+ // but it's not an Option or a Result, so bail
+ return;
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = format!(
+ "unwrap_or({})",
+ snippet_with_applicability(cx, or_arg_content, "..", &mut applicability)
+ );
+
+ span_lint_and_sugg(
+ cx,
+ OR_THEN_UNWRAP,
+ unwrap_expr.span.with_lo(or_span.lo()),
+ title,
+ "try this",
+ suggestion,
+ applicability,
+ );
+}
+
+fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option<Span> {
+ if let ExprKind::Call(some_expr, [arg]) = expr.kind
++ && is_res_lang_ctor(cx, path_res(cx, some_expr), item)
+ {
+ Some(arg.span)
+ } else {
+ None
+ }
+}
--- /dev/null
- let msg = format!(
- "called `{}()` after searching an `Iterator` with `{}`",
- option_check_method, search_method
- );
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::sugg::deref_closure_args;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_trait_method, strip_pat_refs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::PatKind;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+use super::SEARCH_IS_SOME;
+
+/// lint searching an Iterator followed by `is_some()`
+/// or calling `find()` on a string followed by `is_some()` or `is_none()`
+#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'_>,
+ expr: &'tcx hir::Expr<'_>,
+ search_method: &str,
+ is_some: bool,
+ search_recv: &hir::Expr<'_>,
+ search_arg: &'tcx hir::Expr<'_>,
+ is_some_recv: &hir::Expr<'_>,
+ method_span: Span,
+) {
+ let option_check_method = if is_some { "is_some" } else { "is_none" };
+ // lint if caller of search is an Iterator
+ if is_trait_method(cx, is_some_recv, sym::Iterator) {
- "!{}.any({})",
- iter,
++ let msg = format!("called `{option_check_method}()` after searching an `Iterator` with `{search_method}`");
+ let search_snippet = snippet(cx, search_arg.span, "..");
+ if search_snippet.lines().count() <= 1 {
+ // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
+ // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
+ let mut applicability = Applicability::MachineApplicable;
+ let any_search_snippet = if_chain! {
+ if search_method == "find";
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind;
+ let closure_body = cx.tcx.hir().body(body);
+ if let Some(closure_arg) = closure_body.params.get(0);
+ then {
+ if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
+ Some(search_snippet.replacen('&', "", 1))
+ } else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind {
+ // `find()` provides a reference to the item, but `any` does not,
+ // so we should fix item usages for suggestion
+ if let Some(closure_sugg) = deref_closure_args(cx, search_arg) {
+ applicability = closure_sugg.applicability;
+ Some(closure_sugg.suggestion)
+ } else {
+ Some(search_snippet.to_string())
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ };
+ // add note if not multi-line
+ if is_some {
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ method_span.with_hi(expr.span.hi()),
+ &msg,
+ "use `any()` instead",
+ format!(
+ "any({})",
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
+ applicability,
+ );
+ } else {
+ let iter = snippet(cx, search_recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ expr.span,
+ &msg,
+ "use `!_.any()` instead",
+ format!(
- let msg = format!("called `{}()` after calling `find()` on a string", option_check_method);
++ "!{iter}.any({})",
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
+ applicability,
+ );
+ }
+ } else {
+ let hint = format!(
+ "this is more succinctly expressed by calling `any()`{}",
+ if option_check_method == "is_none" {
+ " with negation"
+ } else {
+ ""
+ }
+ );
+ span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, &hint);
+ }
+ }
+ // lint if `find()` is called by `String` or `&str`
+ else if search_method == "find" {
+ let is_string_or_str_slice = |e| {
+ let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
+ if is_type_diagnostic_item(cx, self_ty, sym::String) {
+ true
+ } else {
+ *self_ty.kind() == ty::Str
+ }
+ };
+ if_chain! {
+ if is_string_or_str_slice(search_recv);
+ if is_string_or_str_slice(search_arg);
+ then {
- format!("contains({})", find_arg),
++ let msg = format!("called `{option_check_method}()` after calling `find()` on a string");
+ match option_check_method {
+ "is_some" => {
+ let mut applicability = Applicability::MachineApplicable;
+ let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ method_span.with_hi(expr.span.hi()),
+ &msg,
+ "use `contains()` instead",
- format!("!{}.contains({})", string, find_arg),
++ format!("contains({find_arg})"),
+ applicability,
+ );
+ },
+ "is_none" => {
+ let string = snippet(cx, search_recv.span, "..");
+ let mut applicability = Applicability::MachineApplicable;
+ let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ expr.span,
+ &msg,
+ "use `!_.contains()` instead",
++ format!("!{string}.contains({find_arg})"),
+ applicability,
+ );
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
--- /dev/null
- let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string);
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::SINGLE_CHAR_ADD_STR;
+
+/// lint for length-1 `str`s as argument for `insert_str`
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) {
+ let base_string_snippet =
+ snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability);
+ let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
++ let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})");
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_ADD_STR,
+ expr.span,
+ "calling `insert_str()` using a single-character string literal",
+ "consider using `insert` with a character literal",
+ sugg,
+ applicability,
+ );
+ }
+}
--- /dev/null
- let sugg = format!("{}.push({})", base_string_snippet, extension_string);
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::SINGLE_CHAR_ADD_STR;
+
+/// lint for length-1 `str`s as argument for `push_str`
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[0], &mut applicability) {
+ let base_string_snippet =
+ snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
++ let sugg = format!("{base_string_snippet}.push({extension_string})");
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_ADD_STR,
+ expr.span,
+ "calling `push_str()` using a single-character string literal",
+ "consider using `push` with a character literal",
+ sugg,
+ applicability,
+ );
+ }
+}
--- /dev/null
- &format!("used `sort` on primitive type `{}`", slice_type),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::is_slice_of_primitives;
+use clippy_utils::source::snippet_with_context;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+
+use super::STABLE_SORT_PRIMITIVE;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
+ if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
+ && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
+ && cx.tcx.type_of(impl_id).is_slice()
+ && let Some(slice_type) = is_slice_of_primitives(cx, recv)
+ {
+ span_lint_and_then(
+ cx,
+ STABLE_SORT_PRIMITIVE,
+ e.span,
- diag.span_suggestion(e.span, "try", format!("{}.sort_unstable()", recv_snip), app);
++ &format!("used `sort` on primitive type `{slice_type}`"),
+ |diag| {
+ let mut app = Applicability::MachineApplicable;
+ let recv_snip = snippet_with_context(cx, recv.span, e.span.ctxt(), "..", &mut app).0;
++ diag.span_suggestion(e.span, "try", format!("{recv_snip}.sort_unstable()"), app);
+ diag.note(
+ "an unstable sort typically performs faster without any observable difference for this data type",
+ );
+ },
+ );
+ }
+}
--- /dev/null
- use clippy_utils::visitors::expr_visitor;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::usage::local_used_after_expr;
- use rustc_hir::intravisit::Visitor;
++use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
+use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
++use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- if let StmtKind::Local(Local {
+use rustc_hir::{
+ BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
+};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Span, Symbol, SyntaxContext};
+
+use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+ count: u128,
+ msrv: Option<RustcVersion>,
+) {
+ if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
+ return;
+ }
+
+ let needless = |usage_kind| match usage_kind {
+ IterUsageKind::Nth(n) => count > n + 1,
+ IterUsageKind::NextTuple => count > 2,
+ };
+ let manual = count == 2 && meets_msrv(msrv, msrvs::STR_SPLIT_ONCE);
+
+ match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) {
+ Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),
+ Some(usage) if manual => check_manual_split_once(cx, method_name, expr, self_arg, pat_arg, &usage),
+ None if manual => {
+ check_manual_split_once_indirect(cx, method_name, expr, self_arg, pat_arg);
+ },
+ _ => {},
+ }
+}
+
+fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
+ let mut app = Applicability::MachineApplicable;
+ let r = if method_name == "splitn" { "" } else { "r" };
+
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_SPLITN,
+ expr.span,
+ &format!("unnecessary use of `{r}splitn`"),
+ "try this",
+ format!(
+ "{}.{r}split({})",
+ snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut app).0,
+ snippet_with_context(cx, pat_arg.span, expr.span.ctxt(), "..", &mut app).0,
+ ),
+ app,
+ );
+}
+
+fn check_manual_split_once(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+ usage: &IterUsage,
+) {
+ let ctxt = expr.span.ctxt();
+ let (msg, reverse) = if method_name == "splitn" {
+ ("manual implementation of `split_once`", false)
+ } else {
+ ("manual implementation of `rsplit_once`", true)
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+ let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
+
+ let sugg = match usage.kind {
+ IterUsageKind::NextTuple => {
+ if reverse {
+ format!("{self_snip}.rsplit_once({pat_snip}).map(|(x, y)| (y, x))")
+ } else {
+ format!("{self_snip}.split_once({pat_snip})")
+ }
+ },
+ IterUsageKind::Nth(1) => {
+ let (r, field) = if reverse { ("r", 0) } else { ("", 1) };
+
+ match usage.unwrap_kind {
+ Some(UnwrapKind::Unwrap) => {
+ format!("{self_snip}.{r}split_once({pat_snip}).unwrap().{field}")
+ },
+ Some(UnwrapKind::QuestionMark) => {
+ format!("{self_snip}.{r}split_once({pat_snip})?.{field}")
+ },
+ None => {
+ format!("{self_snip}.{r}split_once({pat_snip}).map(|x| x.{field})")
+ },
+ }
+ },
+ IterUsageKind::Nth(_) => return,
+ };
+
+ span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
+}
+
+/// checks for
+///
+/// ```
+/// let mut iter = "a.b.c".splitn(2, '.');
+/// let a = iter.next();
+/// let b = iter.next();
+/// ```
+fn check_manual_split_once_indirect(
+ cx: &LateContext<'_>,
+ method_name: &str,
+ expr: &Expr<'_>,
+ self_arg: &Expr<'_>,
+ pat_arg: &Expr<'_>,
+) -> Option<()> {
+ let ctxt = expr.span.ctxt();
+ let mut parents = cx.tcx.hir().parent_iter(expr.hir_id);
+ if let (_, Node::Local(local)) = parents.next()?
+ && let PatKind::Binding(BindingAnnotation::MUT, iter_binding_id, iter_ident, None) = local.pat.kind
+ && let (iter_stmt_id, Node::Stmt(_)) = parents.next()?
+ && let (_, Node::Block(enclosing_block)) = parents.next()?
+
+ && let mut stmts = enclosing_block
+ .stmts
+ .iter()
+ .skip_while(|stmt| stmt.hir_id != iter_stmt_id)
+ .skip(1)
+
+ && let first = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
+ && let second = indirect_usage(cx, stmts.next()?, iter_binding_id, ctxt)?
+ && first.unwrap_kind == second.unwrap_kind
+ && first.name != second.name
+ && !local_used_after_expr(cx, iter_binding_id, second.init_expr)
+ {
+ let (r, lhs, rhs) = if method_name == "splitn" {
+ ("", first.name, second.name)
+ } else {
+ ("r", second.name, first.name)
+ };
+ let msg = format!("manual implementation of `{r}split_once`");
+
+ let mut app = Applicability::MachineApplicable;
+ let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+ let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
+
+ span_lint_and_then(cx, MANUAL_SPLIT_ONCE, local.span, &msg, |diag| {
+ diag.span_label(first.span, "first usage here");
+ diag.span_label(second.span, "second usage here");
+
+ let unwrap = match first.unwrap_kind {
+ UnwrapKind::Unwrap => ".unwrap()",
+ UnwrapKind::QuestionMark => "?",
+ };
+ diag.span_suggestion_verbose(
+ local.span,
+ &format!("try `{r}split_once`"),
+ format!("let ({lhs}, {rhs}) = {self_snip}.{r}split_once({pat_snip}){unwrap};"),
+ app,
+ );
+
+ let remove_msg = format!("remove the `{iter_ident}` usages");
+ diag.span_suggestion(
+ first.span,
+ &remove_msg,
+ "",
+ app,
+ );
+ diag.span_suggestion(
+ second.span,
+ &remove_msg,
+ "",
+ app,
+ );
+ });
+ }
+
+ Some(())
+}
+
+#[derive(Debug)]
+struct IndirectUsage<'a> {
+ name: Symbol,
+ span: Span,
+ init_expr: &'a Expr<'a>,
+ unwrap_kind: UnwrapKind,
+}
+
+/// returns `Some(IndirectUsage)` for e.g.
+///
+/// ```ignore
+/// let name = binding.next()?;
+/// let name = binding.next().unwrap();
+/// ```
+fn indirect_usage<'tcx>(
+ cx: &LateContext<'tcx>,
+ stmt: &Stmt<'tcx>,
+ binding: HirId,
+ ctxt: SyntaxContext,
+) -> Option<IndirectUsage<'tcx>> {
- expr_visitor(cx, |expr| {
- if path_to_local_id(expr, binding) {
- path_to_binding = Some(expr);
++ if let StmtKind::Local(&Local {
+ pat: Pat {
+ kind: PatKind::Binding(BindingAnnotation::NONE, _, ident, None),
+ ..
+ },
+ init: Some(init_expr),
+ hir_id: local_hir_id,
+ ..
+ }) = stmt.kind
+ {
+ let mut path_to_binding = None;
-
- path_to_binding.is_none()
- })
- .visit_expr(init_expr);
++ let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| {
++ if path_to_local_id(e, binding) {
++ path_to_binding = Some(e);
+ }
- if parent_id == *local_hir_id {
++ ControlFlow::Continue(Descend::from(path_to_binding.is_none()))
++ });
+
+ let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
+ let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
+
+ let (parent_id, _) = parents.find(|(_, node)| {
+ !matches!(
+ node,
+ Node::Expr(Expr {
+ kind: ExprKind::Match(.., MatchSource::TryDesugar),
+ ..
+ })
+ )
+ })?;
+
+ if let IterUsage {
+ kind: IterUsageKind::Nth(0),
+ unwrap_kind: Some(unwrap_kind),
+ ..
+ } = iter_usage
+ {
++ if parent_id == local_hir_id {
+ return Some(IndirectUsage {
+ name: ident.name,
+ span: stmt.span,
+ init_expr,
+ unwrap_kind,
+ });
+ }
+ }
+ }
+
+ None
+}
+
+#[derive(Debug, Clone, Copy)]
+enum IterUsageKind {
+ Nth(u128),
+ NextTuple,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum UnwrapKind {
+ Unwrap,
+ QuestionMark,
+}
+
+#[derive(Debug)]
+struct IterUsage {
+ kind: IterUsageKind,
+ unwrap_kind: Option<UnwrapKind>,
+ span: Span,
+}
+
+#[allow(clippy::too_many_lines)]
+fn parse_iter_usage<'tcx>(
+ cx: &LateContext<'tcx>,
+ ctxt: SyntaxContext,
+ mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
+) -> Option<IterUsage> {
+ let (kind, span) = match iter.next() {
+ Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
+ let (name, args) = if let ExprKind::MethodCall(name, _, [args @ ..], _) = e.kind {
+ (name, args)
+ } else {
+ return None;
+ };
+ let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
+ let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
+
+ match (name.ident.as_str(), args) {
+ ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
+ ("next_tuple", []) => {
+ return if_chain! {
+ if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
+ if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
+ if let ty::Tuple(subs) = subs.type_at(0).kind();
+ if subs.len() == 2;
+ then {
+ Some(IterUsage {
+ kind: IterUsageKind::NextTuple,
+ span: e.span,
+ unwrap_kind: None
+ })
+ } else {
+ None
+ }
+ };
+ },
+ ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
+ if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
+ let span = if name.ident.as_str() == "nth" {
+ e.span
+ } else {
+ if_chain! {
+ if let Some((_, Node::Expr(next_expr))) = iter.next();
+ if let ExprKind::MethodCall(next_name, _, [], _) = next_expr.kind;
+ if next_name.ident.name == sym::next;
+ if next_expr.span.ctxt() == ctxt;
+ if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
+ if cx.tcx.trait_of_item(next_id) == Some(iter_id);
+ then {
+ next_expr.span
+ } else {
+ return None;
+ }
+ }
+ };
+ (IterUsageKind::Nth(idx), span)
+ } else {
+ return None;
+ }
+ },
+ _ => return None,
+ }
+ },
+ _ => return None,
+ };
+
+ let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
+ match e.kind {
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
+ ..
+ },
+ _,
+ ) => {
+ let parent_span = e.span.parent_callsite().unwrap();
+ if parent_span.ctxt() == ctxt {
+ (Some(UnwrapKind::QuestionMark), parent_span)
+ } else {
+ (None, span)
+ }
+ },
+ _ if e.span.ctxt() != ctxt => (None, span),
+ ExprKind::MethodCall(name, _, [], _)
+ if name.ident.name == sym::unwrap
+ && cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
+ {
+ (Some(UnwrapKind::Unwrap), e.span)
+ },
+ _ => (None, span),
+ }
+ } else {
+ (None, span)
+ };
+
+ Some(IterUsage {
+ kind,
+ unwrap_kind,
+ span,
+ })
+}
--- /dev/null
- "{}.push_str({}{})",
+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_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+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_diagnostic_item(cx, obj_ty, sym::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 {
+ ""
+ } else if is_type_diagnostic_item(cx, self_ty, sym::String) {
+ "&"
+ } else {
+ return;
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ STRING_EXTEND_CHARS,
+ expr.span,
+ "calling `.extend(_.chars())`",
+ "try this",
+ format!(
- ref_str,
++ "{}.push_str({ref_str}{})",
+ snippet_with_applicability(cx, recv.span, "..", &mut applicability),
+ snippet_with_applicability(cx, target.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
- (format!("`{}` called with `0` splits", method_name),
+use clippy_utils::diagnostics::span_lint_and_note;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::source_map::Spanned;
+
+use super::SUSPICIOUS_SPLITN;
+
+pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
+ if_chain! {
+ if count <= 1;
+ if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
+ if cx.tcx.impl_trait_ref(impl_id).is_none();
+ let self_ty = cx.tcx.type_of(impl_id);
+ if self_ty.is_slice() || self_ty.is_str();
+ then {
+ // Ignore empty slice and string literals when used with a literal count.
+ if matches!(self_arg.kind, ExprKind::Array([]))
+ || matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
+ {
+ return;
+ }
+
+ let (msg, note_msg) = if count == 0 {
- (format!("`{}` called with `1` split", method_name),
++ (format!("`{method_name}` called with `0` splits"),
+ "the resulting iterator will always return `None`")
+ } else {
++ (format!("`{method_name}` called with `1` split"),
+ if self_ty.is_slice() {
+ "the resulting iterator will always return the entire slice followed by `None`"
+ } else {
+ "the resulting iterator will always return the entire string followed by `None`"
+ })
+ };
+
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_SPLITN,
+ expr.span,
+ &msg,
+ None,
+ note_msg,
+ );
+ }
+ }
+}
--- /dev/null
- &format!("this `to_owned` call clones the {0} itself and does not cause the {0} contents to become owned", input_type),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_diag_trait_item;
+use clippy_utils::source::snippet_with_context;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+use super::SUSPICIOUS_TO_OWNED;
+
+pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if is_diag_trait_item(cx, method_def_id, sym::ToOwned);
+ let input_type = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind();
+ if cx.tcx.is_diagnostic_item(sym::Cow, adt.did());
+ then {
+ let mut app = Applicability::MaybeIncorrect;
+ let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
+ span_lint_and_sugg(
+ cx,
+ SUSPICIOUS_TO_OWNED,
+ expr.span,
- format!("{0}.clone()` or `{0}.into_owned()", recv_snip),
++ &format!("this `to_owned` call clones the {input_type} itself and does not cause the {input_type} contents to become owned"),
+ "consider using, depending on intent",
++ format!("{recv_snip}.clone()` or `{recv_snip}.into_owned()"),
+ app,
+ );
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
+use super::utils::clone_or_copy_needed;
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_copy;
+use clippy_utils::usage::mutated_variables;
- use rustc_hir::intravisit::{walk_expr, Visitor};
++use clippy_utils::visitors::{for_each_expr, Descend};
++use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
++use core::ops::ControlFlow;
+use rustc_hir as hir;
- pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::sym;
+
+use super::UNNECESSARY_FILTER_MAP;
+use super::UNNECESSARY_FIND_MAP;
+
- let mut return_visitor = ReturnVisitor::new(cx, arg_id);
- return_visitor.visit_expr(body.value);
- found_mapping |= return_visitor.found_mapping;
- found_filtering |= return_visitor.found_filtering;
++pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>, name: &str) {
+ if !is_trait_method(cx, expr, sym::Iterator) {
+ return;
+ }
+
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
+ let body = cx.tcx.hir().body(body);
+ let arg_id = body.params[0].pat.hir_id;
+ let mutates_arg = mutated_variables(body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
+ let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, body.value);
+
+ let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, body.value);
+
- &format!("this `.{}` can be written more simply using `.{}`", name, sugg),
++ let _: Option<!> = for_each_expr(body.value, |e| {
++ if let hir::ExprKind::Ret(Some(e)) = &e.kind {
++ let (found_mapping_res, found_filtering_res) = check_expression(cx, arg_id, e);
++ found_mapping |= found_mapping_res;
++ found_filtering |= found_filtering_res;
++ ControlFlow::Continue(Descend::No)
++ } else {
++ ControlFlow::Continue(Descend::Yes)
++ }
++ });
+
+ let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
+ let sugg = if !found_filtering {
+ if name == "filter_map" { "map" } else { "map(..).next()" }
+ } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
+ match cx.typeck_results().expr_ty(body.value).kind() {
+ ty::Adt(adt, subst)
+ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) =>
+ {
+ if name == "filter_map" { "filter" } else { "find" }
+ },
+ _ => return,
+ }
+ } else {
+ return;
+ };
+ span_lint(
+ cx,
+ if name == "filter_map" {
+ UNNECESSARY_FILTER_MAP
+ } else {
+ UNNECESSARY_FIND_MAP
+ },
+ expr.span,
- match &expr.kind {
++ &format!("this `.{name}` can be written more simply using `.{sugg}`"),
+ );
+ }
+}
+
+// returns (found_mapping, found_filtering)
+fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) {
- if let hir::ExprKind::Path(ref path) = func.kind {
- if is_lang_ctor(cx, path, OptionSome) {
- if path_to_local_id(&args[0], arg_id) {
- return (false, false);
- }
- return (true, false);
++ match expr.kind {
+ hir::ExprKind::Call(func, args) => {
- for arm in *arms {
++ if is_res_lang_ctor(cx, path_res(cx, func), OptionSome) {
++ if path_to_local_id(&args[0], arg_id) {
++ return (false, false);
+ }
++ return (true, false);
+ }
+ (true, true)
+ },
+ hir::ExprKind::Block(block, _) => block
+ .expr
+ .as_ref()
+ .map_or((false, false), |expr| check_expression(cx, arg_id, expr)),
+ hir::ExprKind::Match(_, arms, _) => {
+ let mut found_mapping = false;
+ let mut found_filtering = false;
- hir::ExprKind::Path(path) if is_lang_ctor(cx, path, OptionNone) => (false, true),
++ for arm in arms {
+ let (m, f) = check_expression(cx, arg_id, arm.body);
+ found_mapping |= m;
+ found_filtering |= f;
+ }
+ (found_mapping, found_filtering)
+ },
+ // There must be an else_arm or there will be a type error
+ hir::ExprKind::If(_, if_arm, Some(else_arm)) => {
+ let if_check = check_expression(cx, arg_id, if_arm);
+ let else_check = check_expression(cx, arg_id, else_arm);
+ (if_check.0 | else_check.0, if_check.1 | else_check.1)
+ },
-
- struct ReturnVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- arg_id: hir::HirId,
- // Found a non-None return that isn't Some(input)
- found_mapping: bool,
- // Found a return that isn't Some
- found_filtering: bool,
- }
-
- impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> {
- fn new(cx: &'a LateContext<'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> {
- ReturnVisitor {
- cx,
- arg_id,
- found_mapping: false,
- found_filtering: false,
- }
- }
- }
-
- impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- if let hir::ExprKind::Ret(Some(expr)) = &expr.kind {
- let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
- self.found_mapping |= found_mapping;
- self.found_filtering |= found_filtering;
- } else {
- walk_expr(self, expr);
- }
- }
- }
++ hir::ExprKind::Path(ref path) if is_res_lang_ctor(cx, cx.qpath_res(path, expr.hir_id), OptionNone) => {
++ (false, true)
++ },
+ _ => (true, true),
+ }
+}
--- /dev/null
- "{replacement}(|{s}| {r})",
- replacement = replacement_method_name,
- s = second_arg_ident,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, strip_pat_refs};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::PatKind;
+use rustc_lint::LateContext;
+use rustc_span::{source_map::Span, sym};
+
+use super::UNNECESSARY_FOLD;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ init: &hir::Expr<'_>,
+ acc: &hir::Expr<'_>,
+ fold_span: Span,
+) {
+ fn check_fold_with_op(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ acc: &hir::Expr<'_>,
+ fold_span: Span,
+ op: hir::BinOpKind,
+ replacement_method_name: &str,
+ replacement_has_args: bool,
+ ) {
+ if_chain! {
+ // Extract the body of the closure passed to fold
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind;
+ let closure_body = cx.tcx.hir().body(body);
+ let closure_expr = peel_blocks(closure_body.value);
+
+ // Check if the closure body is of the form `acc <op> some_expr(x)`
+ if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind;
+ if bin_op.node == op;
+
+ // Extract the names of the two arguments to the closure
+ if let [param_a, param_b] = closure_body.params;
+ if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(param_a.pat).kind;
+ if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(param_b.pat).kind;
+
+ if path_to_local_id(left_expr, first_arg_id);
+ if replacement_has_args || path_to_local_id(right_expr, second_arg_id);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = if replacement_has_args {
+ format!(
- "{replacement}()",
- replacement = replacement_method_name,
++ "{replacement_method_name}(|{second_arg_ident}| {r})",
+ r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability),
+ )
+ } else {
+ format!(
++ "{replacement_method_name}()",
+ )
+ };
+
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_FOLD,
+ fold_span.with_hi(expr.span.hi()),
+ // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
+ "this `.fold` can be written more succinctly using another method",
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+
+ // Check that this is a call to Iterator::fold rather than just some function called fold
+ if !is_trait_method(cx, expr, sym::Iterator) {
+ return;
+ }
+
+ // Check if the first argument to .fold is a suitable literal
+ if let hir::ExprKind::Lit(ref lit) = init.kind {
+ match lit.node {
+ ast::LitKind::Bool(false) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, "any", true),
+ ast::LitKind::Bool(true) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, "all", true),
+ ast::LitKind::Int(0, _) => check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, "sum", false),
+ ast::LitKind::Int(1, _) => {
+ check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, "product", false);
+ },
+ _ => (),
+ }
+ }
+}
--- /dev/null
- &format!("unnecessary use of `{}`", method_name),
+use super::utils::clone_or_copy_needed;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::ForLoop;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
+use clippy_utils::{fn_def_id, get_parent_expr};
+use rustc_errors::Applicability;
+use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Symbol};
+
+use super::UNNECESSARY_TO_OWNED;
+
+pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let Some(callee_def_id) = fn_def_id(cx, parent);
+ if is_into_iter(cx, callee_def_id);
+ then {
+ check_for_loop_iter(cx, parent, method_name, receiver, false)
+ } else {
+ false
+ }
+ }
+}
+
+/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the
+/// iterated-over items could be iterated over by reference. The reason why `check` above does not
+/// include this code directly is so that it can be called from
+/// `unnecessary_into_owned::check_into_iter_call_arg`.
+pub fn check_for_loop_iter(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
+ cloned_before_iter: bool,
+) -> bool {
+ if_chain! {
+ if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent));
+ if let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent);
+ let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body);
+ if !clone_or_copy_needed;
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ let snippet = if_chain! {
+ if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind;
+ if maybe_iter_method_name.ident.name == sym::iter;
+
+ if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ if implements_trait(cx, receiver_ty, iterator_trait_id, &[]);
+ if let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty);
+
+ if let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator);
+ let collection_ty = cx.typeck_results().expr_ty(collection);
+ if implements_trait(cx, collection_ty, into_iterator_trait_id, &[]);
+ if let Some(into_iter_item_ty) = get_associated_type(cx, collection_ty, into_iterator_trait_id, "Item");
+
+ if iter_item_ty == into_iter_item_ty;
+ if let Some(collection_snippet) = snippet_opt(cx, collection.span);
+ then {
+ collection_snippet
+ } else {
+ receiver_snippet
+ }
+ };
+ span_lint_and_then(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ expr.span,
++ &format!("unnecessary use of `{method_name}`"),
+ |diag| {
+ // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to
+ // a `to_owned`-like function was removed, then the next suggestion may be
+ // incorrect. This is because the iterator that results from the call's removal
+ // could hold a reference to a resource that is used mutably. See
+ // https://github.com/rust-lang/rust-clippy/issues/8148.
+ let applicability = if cloned_before_iter {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
+ diag.span_suggestion(expr.span, "use", snippet, applicability);
+ for addr_of_expr in addr_of_exprs {
+ match addr_of_expr.kind {
+ ExprKind::AddrOf(_, _, referent) => {
+ let span = addr_of_expr.span.with_hi(referent.span.lo());
+ diag.span_suggestion(span, "remove this `&`", "", applicability);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Returns true if the named method is `IntoIterator::into_iter`.
+pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
+ cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)
+}
--- /dev/null
- use clippy_utils::{eager_or_lazy, usage};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
- &format!("use `{}(..)` instead", simplify_using),
- format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")),
++use clippy_utils::{eager_or_lazy, is_from_proc_macro, usage};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::UNNECESSARY_LAZY_EVALUATIONS;
+
+/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
+/// replaced with `<fn>(return value of simple closure)`
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ simplify_using: &str,
+) {
++ if is_from_proc_macro(cx, expr) {
++ return;
++ }
++
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+ let is_bool = cx.typeck_results().expr_ty(recv).is_bool();
+
+ if is_option || is_result || is_bool {
+ if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
+ let body = cx.tcx.hir().body(body);
+ let body_expr = &body.value;
+
+ if usage::BindingUsageFinder::are_params_used(cx, body) {
+ return;
+ }
+
+ if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
+ let msg = if is_option {
+ "unnecessary closure used to substitute value for `Option::None`"
+ } else if is_result {
+ "unnecessary closure used to substitute value for `Result::Err`"
+ } else {
+ "unnecessary closure used with `bool::then`"
+ };
+ let applicability = if body
+ .params
+ .iter()
+ // bindings are checked to be unused above
+ .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild))
+ {
+ Applicability::MachineApplicable
+ } else {
+ // replacing the lambda may break type inference
+ Applicability::MaybeIncorrect
+ };
+
+ // This is a duplicate of what's happening in clippy_lints::methods::method_call,
+ // which isn't ideal, We want to get the method call span,
+ // but prefer to avoid changing the signature of the function itself.
+ if let hir::ExprKind::MethodCall(.., span) = expr.kind {
+ span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| {
+ diag.span_suggestion(
+ span,
++ &format!("use `{simplify_using}(..)` instead"),
++ format!("{simplify_using}({})", snippet(cx, body_expr.span, "..")),
+ applicability,
+ );
+ });
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir_analysis::check::{FnCtxt, Inherited};
+use super::implicit_clone::is_clone_like;
+use super::unnecessary_iter_cloned::{self, is_into_iter};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
+use clippy_utils::visitors::find_all_ret_expressions;
+use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
++use rustc_hir_analysis::check::{FnCtxt, Inherited};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
+use rustc_middle::ty::EarlyBinder;
+use rustc_middle::ty::{self, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
+use rustc_semver::RustcVersion;
+use rustc_span::{sym, Symbol};
+use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
- &format!("unnecessary use of `{}`", method_name),
+use std::cmp::max;
+
+use super::UNNECESSARY_TO_OWNED;
+
+pub fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ method_name: Symbol,
+ receiver: &'tcx Expr<'_>,
+ args: &'tcx [Expr<'_>],
+ msrv: Option<RustcVersion>,
+) {
+ if_chain! {
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if args.is_empty();
+ then {
+ if is_cloned_or_copied(cx, method_name, method_def_id) {
+ unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
+ } else if is_to_owned_like(cx, expr, method_name, method_def_id) {
+ // At this point, we know the call is of a `to_owned`-like function. The functions
+ // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
+ // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
+ // argument in a `into_iter` call, or an argument in the call of some other function.
+ if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
+ return;
+ }
+ if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
+ return;
+ }
+ check_other_call_arg(cx, expr, method_name, receiver);
+ }
+ }
+ }
+}
+
+/// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
+/// call of a `to_owned`-like function is unnecessary.
+#[allow(clippy::too_many_lines)]
+fn check_addr_of_expr(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ method_def_id: DefId,
+ receiver: &Expr<'_>,
+) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
+ let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
+ if let
+ // For matching uses of `Cow::from`
+ [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ target: target_ty,
+ },
+ ]
+ // For matching uses of arrays
+ | [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Pointer(_),
+ target: target_ty,
+ },
+ ]
+ // For matching everything else
+ | [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ target: referent_ty,
+ },
+ Adjustment {
+ kind: Adjust::Deref(Some(OverloadedDeref { .. })),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Borrow(_),
+ target: target_ty,
+ },
+ ] = adjustments[..];
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty);
+ let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
+ // Only flag cases satisfying at least one of the following three conditions:
+ // * the referent and receiver types are distinct
+ // * the referent/receiver type is a copyable array
+ // * the method is `Cow::into_owned`
+ // This restriction is to ensure there is no overlap between `redundant_clone` and this
+ // lint. It also avoids the following false positive:
+ // https://github.com/rust-lang/rust-clippy/issues/8759
+ // Arrays are a bit of a corner case. Non-copyable arrays are handled by
+ // `redundant_clone`, but copyable arrays are not.
+ if *referent_ty != receiver_ty
+ || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty))
+ || is_cow_into_owned(cx, method_name, method_def_id);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
- "{:&>width$}{}",
++ &format!("unnecessary use of `{method_name}`"),
+ "use",
+ format!(
- receiver_snippet,
++ "{:&>width$}{receiver_snippet}",
+ "",
- &format!("unnecessary use of `{}`", method_name),
+ width = n_target_refs - n_receiver_refs
+ ),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ if_chain! {
+ if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
+ if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
+ if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
+ then {
+ if n_receiver_refs > 0 {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
- &format!("unnecessary use of `{}`", method_name),
++ &format!("unnecessary use of `{method_name}`"),
+ "use",
+ receiver_snippet,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ expr.span.with_lo(receiver.span.hi()),
- &format!("unnecessary use of `{}`", method_name),
++ &format!("unnecessary use of `{method_name}`"),
+ "remove this",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ return true;
+ }
+ }
+ if_chain! {
+ if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
+ if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
- format!("{}.as_ref()", receiver_snippet),
++ &format!("unnecessary use of `{method_name}`"),
+ "use",
- &format!("unnecessary use of `{}`", method_name),
++ format!("{receiver_snippet}.as_ref()"),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+ }
+ }
+ false
+}
+
+/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
+/// call of a `to_owned`-like function is unnecessary.
+fn check_into_iter_call_arg(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ method_name: Symbol,
+ receiver: &Expr<'_>,
+ msrv: Option<RustcVersion>,
+) -> bool {
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let Some(callee_def_id) = fn_def_id(cx, parent);
+ if is_into_iter(cx, callee_def_id);
+ if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ let parent_ty = cx.typeck_results().expr_ty(parent);
+ if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
+ if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
+ return true;
+ }
+ let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
+ "copied"
+ } else {
+ "cloned"
+ };
+ // The next suggestion may be incorrect because the removal of the `to_owned`-like
+ // function could cause the iterator to hold a reference to a resource that is used
+ // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ parent.span,
- format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
++ &format!("unnecessary use of `{method_name}`"),
+ "use",
- &format!("unnecessary use of `{}`", method_name),
++ format!("{receiver_snippet}.iter().{cloned_or_copied}()"),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
+/// of a `to_owned`-like function is unnecessary.
+fn check_other_call_arg<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ method_name: Symbol,
+ receiver: &'tcx Expr<'tcx>,
+) -> bool {
+ if_chain! {
+ if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
+ if let Some((callee_def_id, _, recv, call_args)) = get_callee_substs_and_args(cx, maybe_call);
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ if let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id);
+ if let Some(input) = fn_sig.inputs().get(i);
+ let (input, n_refs) = peel_mid_ty_refs(*input);
+ if let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input);
+ if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
+ if let [trait_predicate] = trait_predicates
+ .iter()
+ .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
+ .collect::<Vec<_>>()[..];
+ if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
+ if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
+ if trait_predicate.def_id() == deref_trait_id || trait_predicate.def_id() == as_ref_trait_id;
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ if can_change_type(cx, maybe_arg, receiver_ty);
+ // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
+ // `Target = T`.
+ if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
+ let n_refs = max(n_refs, usize::from(!is_copy(cx, receiver_ty)));
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_TO_OWNED,
+ maybe_arg.span,
- format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
++ &format!("unnecessary use of `{method_name}`"),
+ "use",
++ format!("{:&>n_refs$}{receiver_snippet}", ""),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+ false
+}
+
+/// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
+/// expression found (if any) along with the immediately prior expression.
+fn skip_addr_of_ancestors<'tcx>(
+ cx: &LateContext<'tcx>,
+ mut expr: &'tcx Expr<'tcx>,
+) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+ while let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
+ expr = parent;
+ } else {
+ return Some((parent, expr));
+ }
+ }
+ None
+}
+
+/// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
+/// `Substs`, and arguments.
+fn get_callee_substs_and_args<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+) -> Option<(DefId, SubstsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
+ if_chain! {
+ if let ExprKind::Call(callee, args) = expr.kind;
+ let callee_ty = cx.typeck_results().expr_ty(callee);
+ if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
+ then {
+ let substs = cx.typeck_results().node_substs(callee.hir_id);
+ return Some((*callee_def_id, substs, None, args));
+ }
+ }
+ if_chain! {
+ if let ExprKind::MethodCall(_, recv, args, _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ then {
+ let substs = cx.typeck_results().node_substs(expr.hir_id);
+ return Some((method_def_id, substs, Some(recv), args));
+ }
+ }
+ None
+}
+
+/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
+fn get_input_traits_and_projections<'tcx>(
+ cx: &LateContext<'tcx>,
+ callee_def_id: DefId,
+ input: Ty<'tcx>,
+) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
+ let mut trait_predicates = Vec::new();
+ let mut projection_predicates = Vec::new();
+ for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
+ match predicate.kind().skip_binder() {
+ PredicateKind::Trait(trait_predicate) => {
+ if trait_predicate.trait_ref.self_ty() == input {
+ trait_predicates.push(trait_predicate);
+ }
+ },
+ PredicateKind::Projection(projection_predicate) => {
+ if projection_predicate.projection_ty.self_ty() == input {
+ projection_predicates.push(projection_predicate);
+ }
+ },
+ _ => {},
+ }
+ }
+ (trait_predicates, projection_predicates)
+}
+
+fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<'a>) -> bool {
+ for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
+ match node {
+ Node::Stmt(_) => return true,
+ Node::Block(..) => continue,
+ Node::Item(item) => {
+ if let ItemKind::Fn(_, _, body_id) = &item.kind
+ && let output_ty = return_ty(cx, item.hir_id())
+ && let local_def_id = cx.tcx.hir().local_def_id(item.hir_id())
+ && Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
+ let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.hir_id());
+ fn_ctxt.can_coerce(ty, output_ty)
+ }) {
+ if has_lifetime(output_ty) && has_lifetime(ty) {
+ return false;
+ }
+ let body = cx.tcx.hir().body(*body_id);
+ let body_expr = &body.value;
+ let mut count = 0;
+ return find_all_ret_expressions(cx, body_expr, |_| { count += 1; count <= 1 });
+ }
+ }
+ Node::Expr(parent_expr) => {
+ if let Some((callee_def_id, call_substs, recv, call_args)) = get_callee_substs_and_args(cx, parent_expr)
+ {
+ if cx.tcx.lang_items().require(LangItem::IntoFutureIntoFuture) == Ok(callee_def_id) {
+ return false;
+ }
+
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+ if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
+ && let Some(param_ty) = fn_sig.inputs().get(arg_index)
+ && let ty::Param(ParamTy { index: param_index , ..}) = param_ty.kind()
+ {
+ if fn_sig
+ .inputs()
+ .iter()
+ .enumerate()
+ .filter(|(i, _)| *i != arg_index)
+ .any(|(_, ty)| ty.contains(*param_ty))
+ {
+ return false;
+ }
+
+ let mut trait_predicates = cx.tcx.param_env(callee_def_id)
+ .caller_bounds().iter().filter(|predicate| {
+ if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && trait_predicate.trait_ref.self_ty() == *param_ty {
+ true
+ } else {
+ false
+ }
+ });
+
+ let new_subst = cx.tcx.mk_substs(
+ call_substs.iter()
+ .enumerate()
+ .map(|(i, t)|
+ if i == (*param_index as usize) {
+ GenericArg::from(ty)
+ } else {
+ t
+ }));
+
+ if trait_predicates.any(|predicate| {
+ let predicate = EarlyBinder(predicate).subst(cx.tcx, new_subst);
+ let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate);
+ !cx.tcx
+ .infer_ctxt()
+ .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation))
+ }) {
+ return false;
+ }
+
+ let output_ty = fn_sig.output();
+ if output_ty.contains(*param_ty) {
+ if let Ok(new_ty) = cx.tcx.try_subst_and_normalize_erasing_regions(
+ new_subst, cx.param_env, output_ty) {
+ expr = parent_expr;
+ ty = new_ty;
+ continue;
+ }
+ return false;
+ }
+
+ return true;
+ }
+ } else if let ExprKind::Block(..) = parent_expr.kind {
+ continue;
+ }
+ return false;
+ },
+ _ => return false,
+ }
+ }
+
+ false
+}
+
+fn has_lifetime(ty: Ty<'_>) -> bool {
+ ty.walk().any(|t| matches!(t.unpack(), GenericArgKind::Lifetime(_)))
+}
+
+/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
+fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
+ && is_diag_trait_item(cx, method_def_id, sym::Iterator)
+}
+
+/// Returns true if the named method can be used to convert the receiver to its "owned"
+/// representation.
+fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool {
+ is_clone_like(cx, method_name.as_str(), method_def_id)
+ || is_cow_into_owned(cx, method_name, method_def_id)
+ || is_to_string_on_string_like(cx, call_expr, method_name, method_def_id)
+}
+
+/// Returns true if the named method is `Cow::into_owned`.
+fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
+ method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
+}
+
+/// Returns true if the named method is `ToString::to_string` and it's called on a type that
+/// is string-like i.e. implements `AsRef<str>` or `Deref<str>`.
+fn is_to_string_on_string_like<'a>(
+ cx: &LateContext<'_>,
+ call_expr: &'a Expr<'a>,
+ method_name: Symbol,
+ method_def_id: DefId,
+) -> bool {
+ if method_name != sym::to_string || !is_diag_trait_item(cx, method_def_id, sym::ToString) {
+ return false;
+ }
+
+ if let Some(substs) = cx.typeck_results().node_substs_opt(call_expr.hir_id)
+ && let [generic_arg] = substs.as_slice()
+ && let GenericArgKind::Type(ty) = generic_arg.unpack()
+ && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
+ && let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
+ && (implements_trait(cx, ty, deref_trait_id, &[cx.tcx.types.str_.into()]) ||
+ implements_trait(cx, ty, as_ref_trait_id, &[cx.tcx.types.str_.into()])) {
+ true
+ } else {
+ false
+ }
+}
--- /dev/null
- use clippy_utils::{get_parent_expr, match_trait_method, paths};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::walk_ptrs_ty_depth;
- if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) {
++use clippy_utils::{get_parent_expr, is_trait_method};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
++use rustc_span::sym;
+
+use super::USELESS_ASREF;
+
+/// Checks for the `USELESS_ASREF` lint.
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
+ // when we get here, we've already checked that the call name is "as_ref" or "as_mut"
+ // check if the call is to the actual `AsRef` or `AsMut` trait
- &format!("this call to `{}` does nothing", call_name),
++ if is_trait_method(cx, expr, sym::AsRef) || is_trait_method(cx, expr, sym::AsMut) {
+ // check if the type after `as_ref` or `as_mut` is the same as before
+ let rcv_ty = cx.typeck_results().expr_ty(recvr);
+ let res_ty = cx.typeck_results().expr_ty(expr);
+ let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty);
+ let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty);
+ if base_rcv_ty == base_res_ty && rcv_depth >= res_depth {
+ // allow the `as_ref` or `as_mut` if it is followed by another method call
+ if_chain! {
+ if let Some(parent) = get_parent_expr(cx, expr);
+ if let hir::ExprKind::MethodCall(segment, ..) = parent.kind;
+ if segment.ident.span != expr.span;
+ then {
+ return;
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ USELESS_ASREF,
+ expr.span,
++ &format!("this call to `{call_name}` does nothing"),
+ "try this",
+ snippet_with_applicability(cx, recvr.span, "..", &mut applicability).to_string(),
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- Self::Eq(this) => format!("`{}`", this).fmt(f),
- Self::StartsWith(this) => format!("`{}*`", this).fmt(f),
- Self::EndsWith(this) => format!("`*{}`", this).fmt(f),
- Self::NotEndsWith(this) => format!("`~{}`", this).fmt(f),
+use crate::methods::SelfKind;
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_copy;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::source_map::Span;
+use std::fmt;
+
+use super::WRONG_SELF_CONVENTION;
+
+#[rustfmt::skip]
+const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
+ (&[Convention::Eq("new")], &[SelfKind::No]),
+ (&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
+ (&[Convention::StartsWith("from_")], &[SelfKind::No]),
+ (&[Convention::StartsWith("into_")], &[SelfKind::Value]),
+ (&[Convention::StartsWith("is_")], &[SelfKind::RefMut, SelfKind::Ref, SelfKind::No]),
+ (&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
+ (&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),
+
+ // Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types).
+ // Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false),
+ Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Ref]),
+ (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true),
+ Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Value]),
+];
+
+enum Convention {
+ Eq(&'static str),
+ StartsWith(&'static str),
+ EndsWith(&'static str),
+ NotEndsWith(&'static str),
+ IsSelfTypeCopy(bool),
+ ImplementsTrait(bool),
+ IsTraitItem(bool),
+}
+
+impl Convention {
+ #[must_use]
+ fn check<'tcx>(
+ &self,
+ cx: &LateContext<'tcx>,
+ self_ty: Ty<'tcx>,
+ other: &str,
+ implements_trait: bool,
+ is_trait_item: bool,
+ ) -> bool {
+ match *self {
+ Self::Eq(this) => this == other,
+ Self::StartsWith(this) => other.starts_with(this) && this != other,
+ Self::EndsWith(this) => other.ends_with(this) && this != other,
+ Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, implements_trait, is_trait_item),
+ Self::IsSelfTypeCopy(is_true) => is_true == is_copy(cx, self_ty),
+ Self::ImplementsTrait(is_true) => is_true == implements_trait,
+ Self::IsTraitItem(is_true) => is_true == is_trait_item,
+ }
+ }
+}
+
+impl fmt::Display for Convention {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match *self {
- format!("method{} implement{} a trait", negation, s_suffix).fmt(f)
++ Self::Eq(this) => format!("`{this}`").fmt(f),
++ Self::StartsWith(this) => format!("`{this}*`").fmt(f),
++ Self::EndsWith(this) => format!("`*{this}`").fmt(f),
++ Self::NotEndsWith(this) => format!("`~{this}`").fmt(f),
+ Self::IsSelfTypeCopy(is_true) => {
+ format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f)
+ },
+ Self::ImplementsTrait(is_true) => {
+ let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") };
- format!("method{} a trait item", suffix).fmt(f)
++ format!("method{negation} implement{s_suffix} a trait").fmt(f)
+ },
+ Self::IsTraitItem(is_true) => {
+ let suffix = if is_true { " is" } else { " is not" };
- "{} usually take {}",
- suggestion,
++ format!("method{suffix} a trait item").fmt(f)
+ },
+ }
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ item_name: &str,
+ self_ty: Ty<'tcx>,
+ first_arg_ty: Ty<'tcx>,
+ first_arg_span: Span,
+ implements_trait: bool,
+ is_trait_item: bool,
+) {
+ if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| {
+ convs
+ .iter()
+ .all(|conv| conv.check(cx, self_ty, item_name, implements_trait, is_trait_item))
+ }) {
+ // don't lint if it implements a trait but not willing to check `Copy` types conventions (see #7032)
+ if implements_trait
+ && !conventions
+ .iter()
+ .any(|conv| matches!(conv, Convention::IsSelfTypeCopy(_)))
+ {
+ return;
+ }
+ if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
+ let suggestion = {
+ if conventions.len() > 1 {
+ // Don't mention `NotEndsWith` when there is also `StartsWith` convention present
+ let cut_ends_with_conv = conventions.iter().any(|conv| matches!(conv, Convention::StartsWith(_)))
+ && conventions
+ .iter()
+ .any(|conv| matches!(conv, Convention::NotEndsWith(_)));
+
+ let s = conventions
+ .iter()
+ .filter_map(|conv| {
+ if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))
+ || matches!(conv, Convention::ImplementsTrait(_))
+ || matches!(conv, Convention::IsTraitItem(_))
+ {
+ None
+ } else {
+ Some(conv.to_string())
+ }
+ })
+ .collect::<Vec<_>>()
+ .join(" and ");
+
+ format!("methods with the following characteristics: ({})", &s)
+ } else {
+ format!("methods called {}", &conventions[0])
+ }
+ };
+
+ span_lint_and_help(
+ cx,
+ WRONG_SELF_CONVENTION,
+ first_arg_span,
+ &format!(
++ "{suggestion} usually take {}",
+ &self_kinds
+ .iter()
+ .map(|k| k.description())
+ .collect::<Vec<_>>()
+ .join(" or ")
+ ),
+ None,
+ "consider choosing a less ambiguous name",
+ );
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{match_trait_method, paths};
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint;
- if cx.typeck_results().expr_ty(receiver).is_floating_point() || match_trait_method(cx, expr, &paths::ORD) {
++use clippy_utils::is_trait_method;
+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::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions where `std::cmp::min` and `max` are
+ /// used to clamp values, but switched so that the result is constant.
+ ///
+ /// ### Why is this bad?
+ /// This is in all probability not the intended outcome. At
+ /// the least it hurts readability of the code.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// min(0, max(100, x))
+ ///
+ /// // or
+ ///
+ /// x.max(100).min(0)
+ /// ```
+ /// It will always be equal to `0`. Probably the author meant to clamp the value
+ /// between 0 and 100, but has erroneously swapped `min` and `max`.
+ #[clippy::version = "pre 1.29.0"]
+ pub MIN_MAX,
+ correctness,
+ "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant"
+}
+
+declare_lint_pass!(MinMaxPass => [MIN_MAX]);
+
+impl<'tcx> LateLintPass<'tcx> for MinMaxPass {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) {
+ if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) {
+ if outer_max == inner_max {
+ return;
+ }
+ match (
+ outer_max,
+ Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c),
+ ) {
+ (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (),
+ _ => {
+ span_lint(
+ cx,
+ MIN_MAX,
+ expr.span,
+ "this `min`/`max` combination leads to constant result",
+ );
+ },
+ }
+ }
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+enum MinMax {
+ Min,
+ Max,
+}
+
+fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
+ match expr.kind {
+ ExprKind::Call(path, args) => {
+ if let ExprKind::Path(ref qpath) = path.kind {
+ cx.typeck_results()
+ .qpath_res(qpath, path.hir_id)
+ .opt_def_id()
+ .and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) {
+ Some(sym::cmp_min) => fetch_const(cx, None, args, MinMax::Min),
+ Some(sym::cmp_max) => fetch_const(cx, None, args, MinMax::Max),
+ _ => None,
+ })
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(path, receiver, args @ [_], _) => {
++ if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) {
+ if path.ident.name == sym!(max) {
+ fetch_const(cx, Some(receiver), args, MinMax::Max)
+ } else if path.ident.name == sym!(min) {
+ fetch_const(cx, Some(receiver), args, MinMax::Min)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn fetch_const<'a>(
+ cx: &LateContext<'_>,
+ receiver: Option<&'a Expr<'a>>,
+ args: &'a [Expr<'a>],
+ m: MinMax,
+) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
+ let mut args = receiver.into_iter().chain(args);
+ let first_arg = args.next()?;
+ let second_arg = args.next()?;
+ if args.next().is_some() {
+ return None;
+ }
+ constant_simple(cx, cx.typeck_results(), first_arg).map_or_else(
+ || constant_simple(cx, cx.typeck_results(), second_arg).map(|c| (m, c, first_arg)),
+ |c| {
+ if constant_simple(cx, cx.typeck_results(), second_arg).is_none() {
+ // otherwise ignore
+ Some((m, c, second_arg))
+ } else {
+ None
+ }
+ },
+ )
+}
--- /dev/null
- use rustc_ast::ast::LitKind;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
+use clippy_utils::source::{snippet, snippet_opt};
+use if_chain::if_chain;
- use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind,
+ Stmt, StmtKind, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::hygiene::DesugaringKind;
+use rustc_span::source_map::{ExpnKind, Span};
+
+use clippy_utils::sugg::Sugg;
- format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, ".."))
++use clippy_utils::{get_parent_expr, in_constant, is_integer_literal, iter_input_pats, last_path_segment, SpanlessEq};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function arguments and let bindings denoted as
+ /// `ref`.
+ ///
+ /// ### Why is this bad?
+ /// The `ref` declaration makes the function take an owned
+ /// value, but turns the argument into a reference (which means that the value
+ /// is destroyed when exiting the function). This adds not much value: either
+ /// take a reference type, or take an owned value and create references in the
+ /// body.
+ ///
+ /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The
+ /// type of `x` is more obvious with the former.
+ ///
+ /// ### Known problems
+ /// If the argument is dereferenced within the function,
+ /// removing the `ref` will lead to errors. This can be fixed by removing the
+ /// dereferences, e.g., changing `*x` to `x` within the function.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(ref _x: u8) {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn foo(_x: &u8) {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TOPLEVEL_REF_ARG,
+ style,
+ "an entire binding declared as `ref`, in a function argument or a `let` statement"
+}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of bindings with a single leading
+ /// underscore.
+ ///
+ /// ### Why is this bad?
+ /// A single leading underscore is usually used to indicate
+ /// that a binding will not be used. Using such a binding breaks this
+ /// expectation.
+ ///
+ /// ### Known problems
+ /// The lint does not work properly with desugaring and
+ /// macro, it has been allowed in the mean time.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _x = 0;
+ /// let y = _x + 1; // Here we are using `_x`, even though it has a leading
+ /// // underscore. We should rename `_x` to `x`
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub USED_UNDERSCORE_BINDING,
+ pedantic,
+ "using a binding which is prefixed with an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of short circuit boolean conditions as
+ /// a
+ /// statement.
+ ///
+ /// ### Why is this bad?
+ /// Using a short circuit boolean condition as a statement
+ /// may hide the fact that the second part is executed or not depending on the
+ /// outcome of the first part.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// f() && g(); // We should write `if f() { g(); }`.
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SHORT_CIRCUIT_STATEMENT,
+ complexity,
+ "using a short circuit boolean condition as a statement"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Catch casts from `0` to some pointer type
+ ///
+ /// ### Why is this bad?
+ /// This generally means `null` and is better expressed as
+ /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = 0 as *const u32;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a = std::ptr::null::<u32>();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ZERO_PTR,
+ style,
+ "using `0 as *{const, mut} T`"
+}
+
+declare_lint_pass!(MiscLints => [
+ TOPLEVEL_REF_ARG,
+ USED_UNDERSCORE_BINDING,
+ SHORT_CIRCUIT_STATEMENT,
+ ZERO_PTR,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for MiscLints {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ k: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if let FnKind::Closure = k {
+ // Does not apply to closures
+ return;
+ }
+ if in_external_macro(cx.tcx.sess, span) {
+ return;
+ }
+ for arg in iter_input_pats(decl, body) {
+ if let PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..) = arg.pat.kind {
+ span_lint(
+ cx,
+ TOPLEVEL_REF_ARG,
+ arg.pat.span,
+ "`ref` directly on a function argument is ignored. \
+ Consider using a reference type instead",
+ );
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, stmt.span);
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., name, None) = local.pat.kind;
+ if let Some(init) = local.init;
+ then {
+ // use the macro callsite when the init span (but not the whole local span)
+ // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
+ let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, init, "..")
+ } else {
+ Sugg::hir(cx, init, "..")
+ };
+ let (mutopt, initref) = if mutabl == Mutability::Mut {
+ ("mut ", sugg_init.mut_addr())
+ } else {
+ ("", sugg_init.addr())
+ };
+ let tyopt = if let Some(ty) = local.ty {
- tyopt=tyopt,
- initref=initref,
++ format!(": &{mutopt}{ty}", ty=snippet(cx, ty.span, ".."))
+ } else {
+ String::new()
+ };
+ span_lint_hir_and_then(
+ cx,
+ TOPLEVEL_REF_ARG,
+ init.hir_id,
+ local.pat.span,
+ "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
+ |diag| {
+ diag.span_suggestion(
+ stmt.span,
+ "try",
+ format!(
+ "let {name}{tyopt} = {initref};",
+ name=snippet(cx, name.span, ".."),
- "if {} {{ {}; }}",
- sugg,
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+ };
+ if_chain! {
+ if let StmtKind::Semi(expr) = stmt.kind;
+ if let ExprKind::Binary(ref binop, a, b) = expr.kind;
+ if binop.node == BinOpKind::And || binop.node == BinOpKind::Or;
+ if let Some(sugg) = Sugg::hir_opt(cx, a);
+ then {
+ span_lint_hir_and_then(
+ cx,
+ SHORT_CIRCUIT_STATEMENT,
+ expr.hir_id,
+ stmt.span,
+ "boolean short circuit operator in statement may be clearer using an explicit test",
+ |diag| {
+ let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg };
+ diag.span_suggestion(
+ stmt.span,
+ "replace it with",
+ format!(
- "used binding `{}` which is prefixed with an underscore. A leading \
- underscore signals that a binding will not be used",
- binding
++ "if {sugg} {{ {}; }}",
+ &snippet(cx, b.span, ".."),
+ ),
+ Applicability::MachineApplicable, // snippet
+ );
+ });
+ }
+ };
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Cast(e, ty) = expr.kind {
+ check_cast(cx, expr.span, e, ty);
+ return;
+ }
+ if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
+ // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
+ return;
+ }
+ let sym;
+ let binding = match expr.kind {
+ ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
+ let binding = last_path_segment(qpath).ident.as_str();
+ if binding.starts_with('_') &&
+ !binding.starts_with("__") &&
+ binding != "_result" && // FIXME: #944
+ is_used(cx, expr) &&
+ // don't lint if the declaration is in a macro
+ non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
+ {
+ Some(binding)
+ } else {
+ None
+ }
+ },
+ ExprKind::Field(_, ident) => {
+ sym = ident.name;
+ let name = sym.as_str();
+ if name.starts_with('_') && !name.starts_with("__") {
+ Some(name)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+ if let Some(binding) = binding {
+ span_lint(
+ cx,
+ USED_UNDERSCORE_BINDING,
+ expr.span,
+ &format!(
- if let ExprKind::Lit(ref lit) = e.kind;
- if let LitKind::Int(0, _) = lit.node;
++ "used binding `{binding}` which is prefixed with an underscore. A leading \
++ underscore signals that a binding will not be used"
+ ),
+ );
+ }
+ }
+}
+
+/// Heuristic to see if an expression is used. Should be compatible with
+/// `unused_variables`'s idea
+/// of what it means for an expression to be "used".
+fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
+ ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
+ _ => is_used(cx, parent),
+ })
+}
+
+/// Tests whether an expression is in a macro expansion (e.g., something
+/// generated by `#[derive(...)]` or the like).
+fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
+ use rustc_span::hygiene::MacroKind;
+ if expr.span.from_expansion() {
+ let data = expr.span.ctxt().outer_expn_data();
+ matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
+ } else {
+ false
+ }
+}
+
+/// Tests whether `res` is a variable defined outside a macro.
+fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
+ if let def::Res::Local(id) = res {
+ !cx.tcx.hir().span(id).from_expansion()
+ } else {
+ false
+ }
+}
+
+fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
+ if_chain! {
+ if let TyKind::Ptr(ref mut_ty) = ty.kind;
- (format!("{}()", sugg_fn), Applicability::MachineApplicable)
++ if is_integer_literal(e, 0);
+ if !in_constant(cx, e.hir_id);
+ then {
+ let (msg, sugg_fn) = match mut_ty.mutbl {
+ Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"),
+ Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"),
+ };
+
+ let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
- (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable)
++ (format!("{sugg_fn}()"), Applicability::MachineApplicable)
+ } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
- (format!("{}()", sugg_fn), Applicability::MaybeIncorrect)
++ (format!("{sugg_fn}::<{mut_ty_snip}>()"), Applicability::MachineApplicable)
+ } else {
+ // `MaybeIncorrect` as type inference may not work with the suggested code
++ (format!("{sugg_fn}()"), Applicability::MaybeIncorrect)
+ };
+ span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
+ }
+ }
+}
--- /dev/null
- &format!("{} type suffix should not be separated by an underscore", sugg_type),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast::ast::Lit;
+use rustc_errors::Applicability;
+use rustc_lint::EarlyContext;
+
+use super::{SEPARATED_LITERAL_SUFFIX, UNSEPARATED_LITERAL_SUFFIX};
+
+pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &str, sugg_type: &str) {
+ let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
+ val
+ } else {
+ return; // It's useless so shouldn't lint.
+ };
+ // Do not lint when literal is unsuffixed.
+ if !suffix.is_empty() {
+ if lit_snip.as_bytes()[maybe_last_sep_idx] == b'_' {
+ span_lint_and_sugg(
+ cx,
+ SEPARATED_LITERAL_SUFFIX,
+ lit.span,
- format!("{}{}", &lit_snip[..maybe_last_sep_idx], suffix),
++ &format!("{sugg_type} type suffix should not be separated by an underscore"),
+ "remove the underscore",
- &format!("{} type suffix should be separated by an underscore", sugg_type),
++ format!("{}{suffix}", &lit_snip[..maybe_last_sep_idx]),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ UNSEPARATED_LITERAL_SUFFIX,
+ lit.span,
- format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
++ &format!("{sugg_type} type suffix should be separated by an underscore"),
+ "add an underscore",
++ format!("{}_{suffix}", &lit_snip[..=maybe_last_sep_idx]),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- "`{}` already exists, having another argument having almost the same \
- name makes code comprehension and documentation more difficult",
- arg_name
+mod builtin_type_shadow;
+mod double_neg;
+mod literal_suffix;
+mod mixed_case_hex_literals;
+mod redundant_pattern;
+mod unneeded_field_pattern;
+mod unneeded_wildcard_pattern;
+mod zero_prefixed_literal;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast::{Expr, ExprKind, Generics, Lit, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
+use rustc_ast::visit::FnKind;
+use rustc_data_structures::fx::FxHashMap;
+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::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for structure field patterns bound to wildcards.
+ ///
+ /// ### Why is this bad?
+ /// Using `..` instead is shorter and leaves the focus on
+ /// the fields that are actually bound.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Foo {
+ /// # a: i32,
+ /// # b: i32,
+ /// # c: i32,
+ /// # }
+ /// let f = Foo { a: 0, b: 0, c: 0 };
+ ///
+ /// match f {
+ /// Foo { a: _, b: 0, .. } => {},
+ /// Foo { a: _, b: _, c: _ } => {},
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # struct Foo {
+ /// # a: i32,
+ /// # b: i32,
+ /// # c: i32,
+ /// # }
+ /// let f = Foo { a: 0, b: 0, c: 0 };
+ ///
+ /// match f {
+ /// Foo { b: 0, .. } => {},
+ /// Foo { .. } => {},
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNEEDED_FIELD_PATTERN,
+ restriction,
+ "struct fields bound to a wildcard instead of using `..`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function arguments having the similar names
+ /// differing by an underscore.
+ ///
+ /// ### Why is this bad?
+ /// It affects code readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(a: i32, _a: i32) {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn bar(a: i32, _b: i32) {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DUPLICATE_UNDERSCORE_ARGUMENT,
+ style,
+ "function arguments having names which only differ by an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects expressions of the form `--x`.
+ ///
+ /// ### Why is this bad?
+ /// It can mislead C/C++ programmers to think `x` was
+ /// decremented.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut x = 3;
+ /// --x;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DOUBLE_NEG,
+ style,
+ "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns on hexadecimal literals with mixed-case letter
+ /// digits.
+ ///
+ /// ### Why is this bad?
+ /// It looks confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let _ =
+ /// 0x1a9BAcD
+ /// # ;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let _ =
+ /// 0x1A9BACD
+ /// # ;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MIXED_CASE_HEX_LITERALS,
+ style,
+ "hex literals whose letter digits are not consistently upper- or lowercased"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if literal suffixes are not separated by an
+ /// underscore.
+ /// To enforce unseparated literal suffix style,
+ /// see the `separated_literal_suffix` lint.
+ ///
+ /// ### Why is this bad?
+ /// Suffix style should be consistent.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let _ =
+ /// 123832i32
+ /// # ;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let _ =
+ /// 123832_i32
+ /// # ;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNSEPARATED_LITERAL_SUFFIX,
+ restriction,
+ "literals whose suffix is not separated by an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if literal suffixes are separated by an underscore.
+ /// To enforce separated literal suffix style,
+ /// see the `unseparated_literal_suffix` lint.
+ ///
+ /// ### Why is this bad?
+ /// Suffix style should be consistent.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let _ =
+ /// 123832_i32
+ /// # ;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let _ =
+ /// 123832i32
+ /// # ;
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub SEPARATED_LITERAL_SUFFIX,
+ restriction,
+ "literals whose suffix is separated by an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if an integral constant literal starts with `0`.
+ ///
+ /// ### Why is this bad?
+ /// In some languages (including the infamous C language
+ /// and most of its
+ /// family), this marks an octal constant. In Rust however, this is a decimal
+ /// constant. This could
+ /// be confusing for both the writer and a reader of the constant.
+ ///
+ /// ### Example
+ ///
+ /// In Rust:
+ /// ```rust
+ /// fn main() {
+ /// let a = 0123;
+ /// println!("{}", a);
+ /// }
+ /// ```
+ ///
+ /// prints `123`, while in C:
+ ///
+ /// ```c
+ /// #include <stdio.h>
+ ///
+ /// int main() {
+ /// int a = 0123;
+ /// printf("%d\n", a);
+ /// }
+ /// ```
+ ///
+ /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
+ #[clippy::version = "pre 1.29.0"]
+ pub ZERO_PREFIXED_LITERAL,
+ complexity,
+ "integer literals starting with `0`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if a generic shadows a built-in type.
+ ///
+ /// ### Why is this bad?
+ /// This gives surprising type errors.
+ ///
+ /// ### Example
+ ///
+ /// ```ignore
+ /// impl<u32> Foo<u32> {
+ /// fn impl_func(&self) -> u32 {
+ /// 42
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub BUILTIN_TYPE_SHADOW,
+ style,
+ "shadowing a builtin type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for patterns in the form `name @ _`.
+ ///
+ /// ### Why is this bad?
+ /// It's almost always more readable to just use direct
+ /// bindings.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = Some("abc");
+ /// match v {
+ /// Some(x) => (),
+ /// y @ _ => (),
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let v = Some("abc");
+ /// match v {
+ /// Some(x) => (),
+ /// y => (),
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub REDUNDANT_PATTERN,
+ style,
+ "using `name @ _` in a pattern"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for tuple patterns with a wildcard
+ /// pattern (`_`) is next to a rest pattern (`..`).
+ ///
+ /// _NOTE_: While `_, ..` means there is at least one element left, `..`
+ /// means there are 0 or more elements left. This can make a difference
+ /// when refactoring, but shouldn't result in errors in the refactored code,
+ /// since the wildcard pattern isn't used anyway.
+ ///
+ /// ### Why is this bad?
+ /// The wildcard pattern is unneeded as the rest pattern
+ /// can match that element as well.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct TupleStruct(u32, u32, u32);
+ /// # let t = TupleStruct(1, 2, 3);
+ /// match t {
+ /// TupleStruct(0, .., _) => (),
+ /// _ => (),
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # struct TupleStruct(u32, u32, u32);
+ /// # let t = TupleStruct(1, 2, 3);
+ /// match t {
+ /// TupleStruct(0, ..) => (),
+ /// _ => (),
+ /// }
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub UNNEEDED_WILDCARD_PATTERN,
+ complexity,
+ "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
+}
+
+declare_lint_pass!(MiscEarlyLints => [
+ UNNEEDED_FIELD_PATTERN,
+ DUPLICATE_UNDERSCORE_ARGUMENT,
+ DOUBLE_NEG,
+ MIXED_CASE_HEX_LITERALS,
+ UNSEPARATED_LITERAL_SUFFIX,
+ SEPARATED_LITERAL_SUFFIX,
+ ZERO_PREFIXED_LITERAL,
+ BUILTIN_TYPE_SHADOW,
+ REDUNDANT_PATTERN,
+ UNNEEDED_WILDCARD_PATTERN,
+]);
+
+impl EarlyLintPass for MiscEarlyLints {
+ fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
+ for param in &gen.params {
+ builtin_type_shadow::check(cx, param);
+ }
+ }
+
+ fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
+ unneeded_field_pattern::check(cx, pat);
+ redundant_pattern::check(cx, pat);
+ unneeded_wildcard_pattern::check(cx, pat);
+ }
+
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
+ let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
+
+ for arg in &fn_kind.decl().inputs {
+ if let PatKind::Ident(_, ident, None) = arg.pat.kind {
+ let arg_name = ident.to_string();
+
+ if let Some(arg_name) = arg_name.strip_prefix('_') {
+ if let Some(correspondence) = registered_names.get(arg_name) {
+ span_lint(
+ cx,
+ DUPLICATE_UNDERSCORE_ARGUMENT,
+ *correspondence,
+ &format!(
++ "`{arg_name}` already exists, having another argument having almost the same \
++ name makes code comprehension and documentation more difficult"
+ ),
+ );
+ }
+ } else {
+ registered_names.insert(arg_name, arg.pat.span);
+ }
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ MiscEarlyLints::check_lit(cx, lit);
+ }
+ double_neg::check(cx, expr);
+ }
+}
+
+impl MiscEarlyLints {
+ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
+ // We test if first character in snippet is a number, because the snippet could be an expansion
+ // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
+ // Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
+ // See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
+ // FIXME: Find a better way to detect those cases.
+ let lit_snip = match snippet_opt(cx, lit.span) {
+ Some(snip) if snip.chars().next().map_or(false, |c| c.is_ascii_digit()) => snip,
+ _ => return,
+ };
+
+ if let LitKind::Int(value, lit_int_type) = lit.kind {
+ let suffix = match lit_int_type {
+ LitIntType::Signed(ty) => ty.name_str(),
+ LitIntType::Unsigned(ty) => ty.name_str(),
+ LitIntType::Unsuffixed => "",
+ };
+ literal_suffix::check(cx, lit, &lit_snip, suffix, "integer");
+ if lit_snip.starts_with("0x") {
+ mixed_case_hex_literals::check(cx, lit, suffix, &lit_snip);
+ } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
+ // nothing to do
+ } else if value != 0 && lit_snip.starts_with('0') {
+ zero_prefixed_literal::check(cx, lit, &lit_snip);
+ }
+ } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
+ let suffix = float_ty.name_str();
+ literal_suffix::check(cx, lit, &lit_snip, suffix, "float");
+ }
+ }
+}
--- /dev/null
- &format!("try with `{} {{ .. }}` instead", type_name),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast::{Pat, PatKind};
+use rustc_lint::EarlyContext;
+
+use super::UNNEEDED_FIELD_PATTERN;
+
+pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
+ if let PatKind::Struct(_, ref npat, ref pfields, _) = pat.kind {
+ let mut wilds = 0;
+ let type_name = npat
+ .segments
+ .last()
+ .expect("A path must have at least one segment")
+ .ident
+ .name;
+
+ for field in pfields {
+ if let PatKind::Wild = field.pat.kind {
+ wilds += 1;
+ }
+ }
+ if !pfields.is_empty() && wilds == pfields.len() {
+ span_lint_and_help(
+ cx,
+ UNNEEDED_FIELD_PATTERN,
+ pat.span,
+ "all the struct fields are matched to a wildcard pattern, consider using `..`",
+ None,
- &format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
++ &format!("try with `{type_name} {{ .. }}` instead"),
+ );
+ return;
+ }
+ if wilds > 0 {
+ for field in pfields {
+ if let PatKind::Wild = field.pat.kind {
+ wilds -= 1;
+ if wilds > 0 {
+ span_lint(
+ cx,
+ UNNEEDED_FIELD_PATTERN,
+ field.span,
+ "you matched a field with a wildcard pattern, consider using `..` instead",
+ );
+ } else {
+ let mut normal = vec![];
+
+ for field in pfields {
+ match field.pat.kind {
+ PatKind::Wild => {},
+ _ => {
+ if let Some(n) = snippet_opt(cx, field.span) {
+ normal.push(n);
+ }
+ },
+ }
+ }
+
+ span_lint_and_help(
+ cx,
+ UNNEEDED_FIELD_PATTERN,
+ field.span,
+ "you matched a field with a wildcard pattern, consider using `..` \
+ instead",
+ None,
++ &format!("try with `{type_name} {{ {}, .. }}`", normal[..].join(", ")),
+ );
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- let msg = format!("`{}` has a similarly named generic type parameter `{}` in its declaration, but in a different order",
- type_name, impl_param_name);
- let help = format!("try `{}`, or a name that does not conflict with `{}`'s generic params",
- type_param_names[i], type_name);
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{GenericArg, Item, ItemKind, QPath, Ty, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::GenericParamDefKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for type parameters which are positioned inconsistently between
+ /// a type definition and impl block. Specifically, a parameter in an impl
+ /// block which has the same name as a parameter in the type def, but is in
+ /// a different place.
+ ///
+ /// ### Why is this bad?
+ /// Type parameters are determined by their position rather than name.
+ /// Naming type parameters inconsistently may cause you to refer to the
+ /// wrong type parameter.
+ ///
+ /// ### Limitations
+ /// This lint only applies to impl blocks with simple generic params, e.g.
+ /// `A`. If there is anything more complicated, such as a tuple, it will be
+ /// ignored.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo<A, B> {
+ /// x: A,
+ /// y: B,
+ /// }
+ /// // inside the impl, B refers to Foo::A
+ /// impl<B, A> Foo<B, A> {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct Foo<A, B> {
+ /// x: A,
+ /// y: B,
+ /// }
+ /// impl<A, B> Foo<A, B> {}
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub MISMATCHING_TYPE_PARAM_ORDER,
+ pedantic,
+ "type parameter positioned inconsistently between type def and impl block"
+}
+declare_lint_pass!(TypeParamMismatch => [MISMATCHING_TYPE_PARAM_ORDER]);
+
+impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if_chain! {
+ if !item.span.from_expansion();
+ if let ItemKind::Impl(imp) = &item.kind;
+ if let TyKind::Path(QPath::Resolved(_, path)) = &imp.self_ty.kind;
+ if let Some(segment) = path.segments.iter().next();
+ if let Some(generic_args) = segment.args;
+ if !generic_args.args.is_empty();
+ then {
+ // get the name and span of the generic parameters in the Impl
+ let mut impl_params = Vec::new();
+ for p in generic_args.args.iter() {
+ match p {
+ GenericArg::Type(Ty {kind: TyKind::Path(QPath::Resolved(_, path)), ..}) =>
+ impl_params.push((path.segments[0].ident.to_string(), path.span)),
+ GenericArg::Type(_) => return,
+ _ => (),
+ };
+ }
+
+ // find the type that the Impl is for
+ // only lint on struct/enum/union for now
+ let defid = match path.res {
+ Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) => defid,
+ _ => return,
+ };
+
+ // get the names of the generic parameters in the type
+ let type_params = &cx.tcx.generics_of(defid).params;
+ let type_param_names: Vec<_> = type_params.iter()
+ .filter_map(|p|
+ match p.kind {
+ GenericParamDefKind::Type {..} => Some(p.name.to_string()),
+ _ => None,
+ }
+ ).collect();
+ // hashmap of name -> index for mismatch_param_name
+ let type_param_names_hashmap: FxHashMap<&String, usize> =
+ type_param_names.iter().enumerate().map(|(i, param)| (param, i)).collect();
+
+ let type_name = segment.ident;
+ for (i, (impl_param_name, impl_param_span)) in impl_params.iter().enumerate() {
+ if mismatch_param_name(i, impl_param_name, &type_param_names_hashmap) {
++ let msg = format!("`{type_name}` has a similarly named generic type parameter `{impl_param_name}` in its declaration, but in a different order");
++ let help = format!("try `{}`, or a name that does not conflict with `{type_name}`'s generic params",
++ type_param_names[i]);
+ span_lint_and_help(
+ cx,
+ MISMATCHING_TYPE_PARAM_ORDER,
+ *impl_param_span,
+ &msg,
+ None,
+ &help
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
+// Checks if impl_param_name is the same as one of type_param_names,
+// and is in a different position
+fn mismatch_param_name(i: usize, impl_param_name: &String, type_param_names: &FxHashMap<&String, usize>) -> bool {
+ if let Some(j) = type_param_names.get(impl_param_name) {
+ if i != *j {
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::qualify_min_const_fn::is_min_const_fn;
+use clippy_utils::ty::has_drop;
+use clippy_utils::{
+ fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, meets_msrv, msrvs, trait_ref_of_method,
+};
+use rustc_hir as hir;
+use rustc_hir::def_id::CRATE_DEF_ID;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests the use of `const` in functions and methods where possible.
+ ///
+ /// ### Why is this bad?
+ /// Not having the function const prevents callers of the function from being const as well.
+ ///
+ /// ### Known problems
+ /// Const functions are currently still being worked on, with some features only being available
+ /// on nightly. This lint does not consider all edge cases currently and the suggestions may be
+ /// incorrect if you are using this lint on stable.
+ ///
+ /// Also, the lint only runs one pass over the code. Consider these two non-const functions:
+ ///
+ /// ```rust
+ /// fn a() -> i32 {
+ /// 0
+ /// }
+ /// fn b() -> i32 {
+ /// a()
+ /// }
+ /// ```
+ ///
+ /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time
+ /// can't be const as it calls a non-const function. Making `a` const and running Clippy again,
+ /// will suggest to make `b` const, too.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Foo {
+ /// # random_number: usize,
+ /// # }
+ /// # impl Foo {
+ /// fn new() -> Self {
+ /// Self { random_number: 42 }
+ /// }
+ /// # }
+ /// ```
+ ///
+ /// Could be a const fn:
+ ///
+ /// ```rust
+ /// # struct Foo {
+ /// # random_number: usize,
+ /// # }
+ /// # impl Foo {
+ /// const fn new() -> Self {
+ /// Self { random_number: 42 }
+ /// }
+ /// # }
+ /// ```
+ #[clippy::version = "1.34.0"]
+ pub MISSING_CONST_FOR_FN,
+ nursery,
+ "Lint functions definitions that could be made `const fn`"
+}
+
+impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]);
+
+pub struct MissingConstForFn {
+ msrv: Option<RustcVersion>,
+}
+
+impl MissingConstForFn {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ _: &FnDecl<'_>,
+ body: &Body<'tcx>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if !meets_msrv(self.msrv, msrvs::CONST_IF_MATCH) {
+ return;
+ }
+
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+
+ if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ // Building MIR for `fn`s with unsatisfiable preds results in ICE.
+ if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ // Perform some preliminary checks that rule out constness on the Clippy side. This way we
+ // can skip the actual const check and return early.
+ match kind {
+ FnKind::ItemFn(_, generics, header, ..) => {
+ let has_const_generic_params = generics
+ .params
+ .iter()
+ .any(|param| matches!(param.kind, GenericParamKind::Const { .. }));
+
+ if already_const(header) || has_const_generic_params {
+ return;
+ }
+ },
+ FnKind::Method(_, sig, ..) => {
+ if trait_ref_of_method(cx, def_id).is_some()
+ || already_const(sig.header)
+ || method_accepts_droppable(cx, sig.decl.inputs)
+ {
+ return;
+ }
+ },
+ FnKind::Closure => return,
+ }
+
+ // Const fns are not allowed as methods in a trait.
+ {
+ let parent = cx.tcx.hir().get_parent_item(hir_id).def_id;
+ if parent != CRATE_DEF_ID {
+ if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent) {
+ if let hir::ItemKind::Trait(..) = &item.kind {
+ return;
+ }
+ }
+ }
+ }
+
+ if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) {
+ return;
+ }
+
+ let mir = cx.tcx.optimized_mir(def_id);
+
+ if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv) {
+ if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
+ cx.tcx.sess.span_err(span, err.as_ref());
+ }
+ } else {
+ span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
+ }
+ }
+ extract_msrv_attr!(LateContext);
+}
+
+/// Returns true if any of the method parameters is a type that implements `Drop`. The method
+/// can't be made const then, because `drop` can't be const-evaluated.
+fn method_accepts_droppable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool {
+ // If any of the params are droppable, return true
+ param_tys.iter().any(|hir_ty| {
+ let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ has_drop(cx, ty_ty)
+ })
+}
+
+// We don't have to lint on something that's already `const`
+#[must_use]
+fn already_const(header: hir::FnHeader) -> bool {
+ header.constness == Constness::Const
+}
--- /dev/null
- &format!("missing documentation for {} {}", article, desc),
+// Note: More specifically this lint is largely inspired (aka copied) from
+// *rustc*'s
+// [`missing_doc`].
+//
+// [`missing_doc`]: https://github.com/rust-lang/rust/blob/cf9cf7c923eb01146971429044f216a3ca905e06/compiler/rustc_lint/src/builtin.rs#L415
+//
+
+use clippy_utils::attrs::is_doc_hidden;
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_from_proc_macro;
+use rustc_ast::ast::{self, MetaItem, MetaItemKind};
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::ty::DefIdTree;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::CRATE_DEF_ID;
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if there is missing doc for any documentable item
+ /// (public or private).
+ ///
+ /// ### Why is this bad?
+ /// Doc is good. *rustc* has a `MISSING_DOCS`
+ /// allowed-by-default lint for
+ /// public members, but has no way to enforce documentation of private items.
+ /// This lint fixes that.
+ #[clippy::version = "pre 1.29.0"]
+ pub MISSING_DOCS_IN_PRIVATE_ITEMS,
+ restriction,
+ "detects missing documentation for public and private members"
+}
+
+pub struct MissingDoc {
+ /// Stack of whether #[doc(hidden)] is set
+ /// at each level which has lint attributes.
+ doc_hidden_stack: Vec<bool>,
+}
+
+impl Default for MissingDoc {
+ #[must_use]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl MissingDoc {
+ #[must_use]
+ pub fn new() -> Self {
+ Self {
+ doc_hidden_stack: vec![false],
+ }
+ }
+
+ fn doc_hidden(&self) -> bool {
+ *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
+ }
+
+ fn has_include(meta: Option<MetaItem>) -> bool {
+ if_chain! {
+ if let Some(meta) = meta;
+ if let MetaItemKind::List(list) = meta.kind;
+ if let Some(meta) = list.get(0);
+ if let Some(name) = meta.ident();
+ then {
+ name.name == sym::include
+ } else {
+ false
+ }
+ }
+ }
+
+ fn check_missing_docs_attrs(
+ &self,
+ cx: &LateContext<'_>,
+ attrs: &[ast::Attribute],
+ sp: Span,
+ article: &'static str,
+ desc: &'static str,
+ ) {
+ // If we're building a test harness, then warning about
+ // documentation is probably not really relevant right now.
+ if cx.sess().opts.test {
+ return;
+ }
+
+ // `#[doc(hidden)]` disables missing_docs check.
+ if self.doc_hidden() {
+ return;
+ }
+
+ if sp.from_expansion() {
+ return;
+ }
+
+ let has_doc = attrs
+ .iter()
+ .any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
+ if !has_doc {
+ span_lint(
+ cx,
+ MISSING_DOCS_IN_PRIVATE_ITEMS,
+ sp,
++ &format!("missing documentation for {article} {desc}"),
+ );
+ }
+ }
+}
+
+impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingDoc {
+ fn enter_lint_attrs(&mut self, _: &LateContext<'tcx>, attrs: &'tcx [ast::Attribute]) {
+ let doc_hidden = self.doc_hidden() || is_doc_hidden(attrs);
+ self.doc_hidden_stack.push(doc_hidden);
+ }
+
+ fn exit_lint_attrs(&mut self, _: &LateContext<'tcx>, _: &'tcx [ast::Attribute]) {
+ self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
+ }
+
+ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
+ let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
+ self.check_missing_docs_attrs(cx, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate");
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
+ match it.kind {
+ hir::ItemKind::Fn(..) => {
+ // ignore main()
+ if it.ident.name == sym::main {
+ let at_root = cx.tcx.local_parent(it.def_id.def_id) == CRATE_DEF_ID;
+ if at_root {
+ return;
+ }
+ }
+ },
+ hir::ItemKind::Const(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Macro(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::Static(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::Trait(..)
+ | hir::ItemKind::TraitAlias(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::OpaqueTy(..) => {},
+ hir::ItemKind::ExternCrate(..)
+ | hir::ItemKind::ForeignMod { .. }
+ | hir::ItemKind::GlobalAsm(..)
+ | hir::ItemKind::Impl { .. }
+ | hir::ItemKind::Use(..) => return,
+ };
+
+ let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id());
+
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ if !is_from_proc_macro(cx, it) {
+ self.check_missing_docs_attrs(cx, attrs, it.span, article, desc);
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
+ let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id());
+
+ let attrs = cx.tcx.hir().attrs(trait_item.hir_id());
+ if !is_from_proc_macro(cx, trait_item) {
+ self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ // If the method is an impl for a trait, don't doc.
+ if let Some(cid) = cx.tcx.associated_item(impl_item.def_id).impl_container(cx.tcx) {
+ if cx.tcx.impl_trait_ref(cid).is_some() {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id());
+ let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
+ if !is_from_proc_macro(cx, impl_item) {
+ self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc);
+ }
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
+ if !sf.is_positional() {
+ let attrs = cx.tcx.hir().attrs(sf.hir_id);
+ if !is_from_proc_macro(cx, sf) {
+ self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field");
+ }
+ }
+ }
+
+ fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
+ let attrs = cx.tcx.hir().attrs(v.id);
+ if !is_from_proc_macro(cx, v) {
+ self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant");
+ }
+ }
+}
--- /dev/null
- if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::<Vec<_>>()) {
+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 {
- "{} as {}",
- import,
- name,
++ let segs = path.split("::").collect::<Vec<_>>();
++ if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, None) {
+ 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
- &format!("missing `#[inline]` for {}", desc),
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast;
+use rustc_hir as hir;
+use rustc_lint::{self, LateContext, LateLintPass, LintContext};
+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
+ /// It lints if an exported function, method, trait method with default impl,
+ /// or trait method impl is not `#[inline]`.
+ ///
+ /// ### Why is this bad?
+ /// In general, it is not. Functions can be inlined across
+ /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled,
+ /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates
+ /// might intend for most of the methods in their public API to be able to be inlined across
+ /// crates even when LTO is disabled. For these types of crates, enabling this lint might make
+ /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and
+ /// then opt out for specific methods where this might not make sense.
+ ///
+ /// ### Example
+ /// ```rust
+ /// pub fn foo() {} // missing #[inline]
+ /// fn ok() {} // ok
+ /// #[inline] pub fn bar() {} // ok
+ /// #[inline(always)] pub fn baz() {} // ok
+ ///
+ /// pub trait Bar {
+ /// fn bar(); // ok
+ /// fn def_bar() {} // missing #[inline]
+ /// }
+ ///
+ /// struct Baz;
+ /// impl Baz {
+ /// fn private() {} // ok
+ /// }
+ ///
+ /// impl Bar for Baz {
+ /// fn bar() {} // ok - Baz is not exported
+ /// }
+ ///
+ /// pub struct PubBaz;
+ /// impl PubBaz {
+ /// fn private() {} // ok
+ /// pub fn not_private() {} // missing #[inline]
+ /// }
+ ///
+ /// impl Bar for PubBaz {
+ /// fn bar() {} // missing #[inline]
+ /// fn def_bar() {} // missing #[inline]
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MISSING_INLINE_IN_PUBLIC_ITEMS,
+ restriction,
+ "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)"
+}
+
+fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) {
+ let has_inline = attrs.iter().any(|a| a.has_name(sym::inline));
+ if !has_inline {
+ span_lint(
+ cx,
+ MISSING_INLINE_IN_PUBLIC_ITEMS,
+ sp,
++ &format!("missing `#[inline]` for {desc}"),
+ );
+ }
+}
+
+fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool {
+ use rustc_session::config::CrateType;
+
+ cx.tcx
+ .sess
+ .crate_types()
+ .iter()
+ .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro))
+}
+
+declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingInline {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
+ if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable_or_proc_macro(cx) {
+ return;
+ }
+
+ if !cx.access_levels.is_exported(it.def_id.def_id) {
+ return;
+ }
+ match it.kind {
+ hir::ItemKind::Fn(..) => {
+ let desc = "a function";
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ check_missing_inline_attrs(cx, attrs, it.span, desc);
+ },
+ hir::ItemKind::Trait(ref _is_auto, ref _unsafe, _generics, _bounds, trait_items) => {
+ // note: we need to check if the trait is exported so we can't use
+ // `LateLintPass::check_trait_item` here.
+ for tit in trait_items {
+ let tit_ = cx.tcx.hir().trait_item(tit.id);
+ match tit_.kind {
+ hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {},
+ hir::TraitItemKind::Fn(..) => {
+ if cx.tcx.impl_defaultness(tit.id.def_id).has_value() {
+ // trait method with default body needs inline in case
+ // an impl is not provided
+ let desc = "a default trait method";
+ let item = cx.tcx.hir().trait_item(tit.id);
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ check_missing_inline_attrs(cx, attrs, item.span, desc);
+ }
+ },
+ }
+ }
+ },
+ hir::ItemKind::Const(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Macro(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::Static(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::TraitAlias(..)
+ | hir::ItemKind::GlobalAsm(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::OpaqueTy(..)
+ | hir::ItemKind::ExternCrate(..)
+ | hir::ItemKind::ForeignMod { .. }
+ | hir::ItemKind::Impl { .. }
+ | hir::ItemKind::Use(..) => {},
+ };
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ use rustc_middle::ty::{ImplContainer, TraitContainer};
+ if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable_or_proc_macro(cx) {
+ return;
+ }
+
+ // If the item being implemented is not exported, then we don't need #[inline]
+ if !cx.access_levels.is_exported(impl_item.def_id.def_id) {
+ return;
+ }
+
+ let desc = match impl_item.kind {
+ hir::ImplItemKind::Fn(..) => "a method",
+ hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(_) => return,
+ };
+
+ let assoc_item = cx.tcx.associated_item(impl_item.def_id);
+ let container_id = assoc_item.container_id(cx.tcx);
+ let trait_def_id = match assoc_item.container {
+ TraitContainer => Some(container_id),
+ ImplContainer => cx.tcx.impl_trait_ref(container_id).map(|t| t.def_id),
+ };
+
+ if let Some(trait_def_id) = trait_def_id {
+ if trait_def_id.is_local() && !cx.access_levels.is_exported(impl_item.def_id.def_id) {
+ // If a trait is being implemented for an item, and the
+ // trait is not exported, we don't need #[inline]
+ return;
+ }
+ }
+
+ let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
+ check_missing_inline_attrs(cx, attrs, impl_item.span, desc);
+ }
+}
--- /dev/null
- |lint| {
- lint.help(format!(
- "move `{}` to `{}`",
- path.display(),
- correct.display(),
- ))
- },
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{FileName, SourceFile, Span, SyntaxContext};
+use std::ffi::OsStr;
+use std::path::{Component, Path};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks that module layout uses only self named module files, bans `mod.rs` files.
+ ///
+ /// ### Why is this bad?
+ /// Having multiple module layout styles in a project can be confusing.
+ ///
+ /// ### Example
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// mod.rs
+ /// lib.rs
+ /// ```
+ /// Use instead:
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// stuff.rs
+ /// lib.rs
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub MOD_MODULE_FILES,
+ restriction,
+ "checks that module layout is consistent"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks that module layout uses only `mod.rs` files.
+ ///
+ /// ### Why is this bad?
+ /// Having multiple module layout styles in a project can be confusing.
+ ///
+ /// ### Example
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// stuff.rs
+ /// lib.rs
+ /// ```
+ /// Use instead:
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// mod.rs
+ /// lib.rs
+ /// ```
+
+ #[clippy::version = "1.57.0"]
+ pub SELF_NAMED_MODULE_FILES,
+ restriction,
+ "checks that module layout is consistent"
+}
+
+pub struct ModStyle;
+
+impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
+
+impl EarlyLintPass for ModStyle {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+ if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow
+ && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow
+ {
+ return;
+ }
+
+ let files = cx.sess().source_map().files();
+
+ let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else { return };
+
+ // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
+ // `[path, to]` but not foo
+ let mut folder_segments = FxHashSet::default();
+ // `mod_folders` is all the unique folder names that contain a mod.rs file
+ let mut mod_folders = FxHashSet::default();
+ // `file_map` maps file names to the full path including the file name
+ // `{ foo => path/to/foo.rs, .. }
+ let mut file_map = FxHashMap::default();
+ for file in files.iter() {
+ if let FileName::Real(name) = &file.name && let Some(lp) = name.local_path() {
+ let path = if lp.is_relative() {
+ lp
+ } else if let Ok(relative) = lp.strip_prefix(trim_to_src) {
+ relative
+ } else {
+ continue;
+ };
+
+ if let Some(stem) = path.file_stem() {
+ file_map.insert(stem, (file, path));
+ }
+ process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
+ check_self_named_mod_exists(cx, path, file);
+ }
+ }
+
+ for folder in &folder_segments {
+ if !mod_folders.contains(folder) {
+ if let Some((file, path)) = file_map.get(folder) {
+ let mut correct = path.to_path_buf();
+ correct.pop();
+ correct.push(folder);
+ correct.push("mod.rs");
+ cx.struct_span_lint(
+ SELF_NAMED_MODULE_FILES,
+ Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
+ format!("`mod.rs` files are required, found `{}`", path.display()),
++ |lint| lint.help(format!("move `{}` to `{}`", path.display(), correct.display(),)),
+ );
+ }
+ }
+ }
+ }
+}
+
+/// For each `path` we add each folder component to `folder_segments` and if the file name
+/// is `mod.rs` we add it's parent folder to `mod_folders`.
+fn process_paths_for_mod_files<'a>(
+ path: &'a Path,
+ folder_segments: &mut FxHashSet<&'a OsStr>,
+ mod_folders: &mut FxHashSet<&'a OsStr>,
+) {
+ let mut comp = path.components().rev().peekable();
+ let _ = comp.next();
+ if path.ends_with("mod.rs") {
+ mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
+ }
+ let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
+ folder_segments.extend(folders);
+}
+
+/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
+fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
+ if path.ends_with("mod.rs") {
+ let mut mod_file = path.to_path_buf();
+ mod_file.pop();
+ mod_file.set_extension("rs");
+
+ cx.struct_span_lint(
+ MOD_MODULE_FILES,
+ Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
+ format!("`mod.rs` files are not allowed, found `{}`", path.display()),
+ |lint| lint.help(format!("move `{}` to `{}`", path.display(), mod_file.display())),
+ );
+ }
+}
--- /dev/null
- &format!("the {} `{}` doesn't need a mutable reference", fn_kind, name),
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects passing a mutable reference to a function that only
+ /// requires an immutable reference.
+ ///
+ /// ### Why is this bad?
+ /// The mutable reference rules out all other references to
+ /// the value. Also the code misleads about the intent of the call site.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut vec = Vec::new();
+ /// # let mut value = 5;
+ /// vec.push(&mut value);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let mut vec = Vec::new();
+ /// # let value = 5;
+ /// vec.push(&value);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_MUT_PASSED,
+ style,
+ "an argument passed as a mutable reference although the callee only demands an immutable reference"
+}
+
+declare_lint_pass!(UnnecessaryMutPassed => [UNNECESSARY_MUT_PASSED]);
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Call(fn_expr, arguments) => {
+ if let ExprKind::Path(ref path) = fn_expr.kind {
+ check_arguments(
+ cx,
+ arguments.iter().collect(),
+ cx.typeck_results().expr_ty(fn_expr),
+ &rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)),
+ "function",
+ );
+ }
+ },
+ ExprKind::MethodCall(path, receiver, arguments, _) => {
+ let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap();
+ let substs = cx.typeck_results().node_substs(e.hir_id);
+ let method_type = cx.tcx.bound_type_of(def_id).subst(cx.tcx, substs);
+ check_arguments(
+ cx,
+ std::iter::once(receiver).chain(arguments.iter()).collect(),
+ method_type,
+ path.ident.as_str(),
+ "method",
+ );
+ },
+ _ => (),
+ }
+ }
+}
+
+fn check_arguments<'tcx>(
+ cx: &LateContext<'tcx>,
+ arguments: Vec<&Expr<'_>>,
+ type_definition: Ty<'tcx>,
+ name: &str,
+ fn_kind: &str,
+) {
+ match type_definition.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
+ for (argument, parameter) in iter::zip(arguments, parameters) {
+ match parameter.kind() {
+ ty::Ref(_, _, Mutability::Not)
+ | ty::RawPtr(ty::TypeAndMut {
+ mutbl: Mutability::Not, ..
+ }) => {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind {
+ span_lint(
+ cx,
+ UNNECESSARY_MUT_PASSED,
+ argument.span,
++ &format!("the {fn_kind} `{name}` doesn't need a mutable reference"),
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+ },
+ _ => (),
+ }
+}
--- /dev/null
- &format!(
- "do not call a function with mutable arguments inside of `{}!`",
- macro_name
- ),
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function/method calls with a mutable
+ /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros.
+ ///
+ /// ### Why is this bad?
+ /// In release builds `debug_assert!` macros are optimized out by the
+ /// compiler.
+ /// Therefore mutating something in a `debug_assert!` macro results in different behavior
+ /// between a release and debug build.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// debug_assert_eq!(vec![3].pop(), Some(3));
+ ///
+ /// // or
+ ///
+ /// # let mut x = 5;
+ /// # fn takes_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
+ /// debug_assert!(takes_a_mut_parameter(&mut x));
+ /// ```
+ #[clippy::version = "1.40.0"]
+ pub DEBUG_ASSERT_WITH_MUT_CALL,
+ nursery,
+ "mutable arguments in `debug_assert{,_ne,_eq}!`"
+}
+
+declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
+
+impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, e) else { return };
+ let macro_name = cx.tcx.item_name(macro_call.def_id);
+ if !matches!(
+ macro_name.as_str(),
+ "debug_assert" | "debug_assert_eq" | "debug_assert_ne"
+ ) {
+ return;
+ }
+ let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn) else { return };
+ for arg in [lhs, rhs] {
+ let mut visitor = MutArgVisitor::new(cx);
+ visitor.visit_expr(arg);
+ if let Some(span) = visitor.expr_span() {
+ span_lint(
+ cx,
+ DEBUG_ASSERT_WITH_MUT_CALL,
+ span,
++ &format!("do not call a function with mutable arguments inside of `{macro_name}!`"),
+ );
+ }
+ }
+ }
+}
+
+struct MutArgVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ expr_span: Option<Span>,
+ found: bool,
+}
+
+impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ expr_span: None,
+ found: false,
+ }
+ }
+
+ fn expr_span(&self) -> Option<Span> {
+ if self.found { self.expr_span } else { None }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
+ self.found = true;
+ return;
+ },
+ ExprKind::If(..) => {
+ self.found = true;
+ return;
+ },
+ ExprKind::Path(_) => {
+ if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
+ if adj
+ .iter()
+ .any(|a| matches!(a.target.kind(), ty::Ref(_, _, Mutability::Mut)))
+ {
+ self.found = true;
+ return;
+ }
+ }
+ },
+ // Don't check await desugars
+ ExprKind::Match(_, _, MatchSource::AwaitDesugar) => return,
+ _ if !self.found => self.expr_span = Some(expr.span),
+ _ => return,
+ }
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
--- /dev/null
- "consider using an `{}` instead of a `Mutex` here; if you just want the locking \
- behavior and not the internal type, consider using `Mutex<()>`",
- atomic_name
+//! Checks for uses of mutex where an atomic value could be used
+//!
+//! This lint is **warn** by default
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Mutex<X>` where an atomic will do.
+ ///
+ /// ### Why is this bad?
+ /// Using a mutex just to make access to a plain bool or
+ /// reference sequential is shooting flies with cannons.
+ /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and
+ /// faster.
+ ///
+ /// ### Known problems
+ /// This lint cannot detect if the mutex is actually used
+ /// for waiting before a critical section.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y = true;
+ /// # use std::sync::Mutex;
+ /// let x = Mutex::new(&y);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let y = true;
+ /// # use std::sync::atomic::AtomicBool;
+ /// let x = AtomicBool::new(y);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUTEX_ATOMIC,
+ nursery,
+ "using a mutex where an atomic value could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Mutex<X>` where `X` is an integral
+ /// type.
+ ///
+ /// ### Why is this bad?
+ /// Using a mutex just to make access to a plain integer
+ /// sequential is
+ /// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster.
+ ///
+ /// ### Known problems
+ /// This lint cannot detect if the mutex is actually used
+ /// for waiting before a critical section.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::sync::Mutex;
+ /// let x = Mutex::new(0usize);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::sync::atomic::AtomicUsize;
+ /// let x = AtomicUsize::new(0usize);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUTEX_INTEGER,
+ nursery,
+ "using a mutex for an integer type"
+}
+
+declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]);
+
+impl<'tcx> LateLintPass<'tcx> for Mutex {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(_, subst) = ty.kind() {
+ if is_type_diagnostic_item(cx, ty, sym::Mutex) {
+ let mutex_param = subst.type_at(0);
+ if let Some(atomic_name) = get_atomic_name(mutex_param) {
+ let msg = format!(
++ "consider using an `{atomic_name}` instead of a `Mutex` here; if you just want the locking \
++ behavior and not the internal type, consider using `Mutex<()>`"
+ );
+ match *mutex_param.kind() {
+ ty::Uint(t) if t != ty::UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg),
+ ty::Int(t) if t != ty::IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg),
+ _ => span_lint(cx, MUTEX_ATOMIC, expr.span, &msg),
+ };
+ }
+ }
+ }
+ }
+}
+
+fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> {
+ match ty.kind() {
+ ty::Bool => Some("AtomicBool"),
+ ty::Uint(_) => Some("AtomicUsize"),
+ ty::Int(_) => Some("AtomicIsize"),
+ ty::RawPtr(_) => Some("AtomicPtr"),
+ _ => None,
+ }
+}
--- /dev/null
- use clippy_utils::source::snippet_with_applicability;
- use if_chain::if_chain;
+use clippy_utils::diagnostics::span_lint_and_then;
- /// Checks for bindings that destructure a reference and borrow the inner
+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
- /// ### Known problems
- /// In some cases, `&ref` is needed to avoid a lifetime mismatch error.
- /// Example:
- /// ```rust
- /// fn foo(a: &Option<String>, b: &Option<String>) {
- /// match (a, b) {
- /// (None, &ref c) | (&ref c, None) => (),
- /// (&Some(ref c), _) => (),
- /// };
- /// }
- /// ```
- ///
++ /// 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.
+ ///
- /// # #[allow(unused)]
+ /// ### Example
+ /// ```rust
+ /// let mut v = Vec::<String>::new();
- /// # #[allow(unused)]
+ /// 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();
- if_chain! {
- // Only lint immutable refs, because `&mut ref T` may be useful.
- if let PatKind::Ref(sub_pat, Mutability::Not) = pat.kind;
+ /// 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 {
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if pat.span.from_expansion() {
+ // OK, simple enough, lints doesn't check in macro.
+ return;
+ }
+
- if let PatKind::Binding(BindingAnnotation::REF, .., spanned_name, _) = sub_pat.kind;
- let parent_id = cx.tcx.hir().get_parent_node(pat.hir_id);
- if let Some(parent_node) = cx.tcx.hir().find(parent_id);
- then {
- // do not recurse within patterns, as they may have other references
- // XXXManishearth we can relax this constraint if we only check patterns
- // with a single ref pattern inside them
- if let Node::Pat(_) = parent_node {
- return;
++ // Do not lint patterns that are part of an OR `|` pattern, the binding mode must match in all arms
++ for (_, node) in cx.tcx.hir().parent_iter(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.
++ let PatKind::Ref(sub_pat, Mutability::Not) = pat.kind else { return };
+
++ match sub_pat.kind {
+ // Check sub_pat got a `ref` keyword (excluding `ref mut`).
- let mut applicability = Applicability::MachineApplicable;
- span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, pat.span,
- "this pattern takes a reference on something that is being de-referenced",
- |diag| {
- let hint = snippet_with_applicability(cx, spanned_name.span, "..", &mut applicability).into_owned();
- diag.span_suggestion(
- pat.span,
- "try removing the `&ref` part and just keep",
- hint,
- applicability,
- );
- });
- }
++ PatKind::Binding(BindingAnnotation::REF, _, ident, None) => {
++ span_lint_and_then(
++ cx,
++ NEEDLESS_BORROWED_REFERENCE,
++ pat.span,
++ "this pattern takes a reference on something that is being dereferenced",
++ |diag| {
++ // `&ref ident`
++ // ^^^^^
++ let span = 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,
++ ) => {
++ 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;
++ }
+ }
++
++ 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()));
++
++ diag.multipart_suggestion(
++ "try removing the `&` and `ref` parts",
++ suggestions,
++ Applicability::MachineApplicable,
++ );
++ },
++ );
++ }
++ },
++ _ => {},
+ }
+ }
+}
--- /dev/null
- &format!("{}\n{}", header, snip),
+//! 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 emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a 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,
- "{indent}if {} {}\n{indent}{}",
- cond_code,
- continue_code,
- else_code,
++ &format!("{header}\n{snip}"),
+ );
+}
+
+fn suggestion_snippet_for_continue_inside_if<'a>(cx: &EarlyContext<'_>, data: &'a 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!(
- .map(|line| format!("{}{}", " ".repeat(indent), line))
++ "{indent}if {cond_code} {continue_code}\n{indent}{else_code}",
+ indent = " ".repeat(indent_if),
+ )
+}
+
+fn suggestion_snippet_for_continue_inside_else<'a>(cx: &EarlyContext<'_>, data: &'a 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()
- "{indent_if}if {} {}\n{indent}// merged code follows:\n{}\n{indent_if}}}",
- cond_code,
- block_code,
- to_annex,
++ .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<'a>(cx: &EarlyContext<'_>, expr: &'a 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
- use clippy_utils::visitors::{expr_visitor, expr_visitor_no_bodies, is_local_used};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::path_to_local;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::needs_ordered_drop;
- use rustc_hir::intravisit::Visitor;
++use clippy_utils::visitors::{for_each_expr, for_each_expr_with_closures, is_local_used};
++use core::ops::ControlFlow;
+use rustc_errors::{Applicability, MultiSpan};
- let mut seen = false;
- expr_visitor(cx, |expr| {
- if let ExprKind::Assign(..) = expr.kind {
- seen = true;
+use rustc_hir::{
+ BindingAnnotation, Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt,
+ StmtKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for late initializations that can be replaced by a `let` statement
+ /// with an initializer.
+ ///
+ /// ### Why is this bad?
+ /// Assigning in the `let` statement is less repetitive.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a;
+ /// a = 1;
+ ///
+ /// let b;
+ /// match 3 {
+ /// 0 => b = "zero",
+ /// 1 => b = "one",
+ /// _ => b = "many",
+ /// }
+ ///
+ /// let c;
+ /// if true {
+ /// c = 1;
+ /// } else {
+ /// c = -1;
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = 1;
+ ///
+ /// let b = match 3 {
+ /// 0 => "zero",
+ /// 1 => "one",
+ /// _ => "many",
+ /// };
+ ///
+ /// let c = if true {
+ /// 1
+ /// } else {
+ /// -1
+ /// };
+ /// ```
+ #[clippy::version = "1.59.0"]
+ pub NEEDLESS_LATE_INIT,
+ style,
+ "late initializations that can be replaced by a `let` statement with an initializer"
+}
+declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
+
+fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
-
- !seen
++ for_each_expr_with_closures(cx, stmt, |e| {
++ if matches!(e.kind, ExprKind::Assign(..)) {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(())
+ }
- .visit_stmt(stmt);
-
- seen
+ })
- let mut seen = false;
- expr_visitor_no_bodies(|expr| {
- if let ExprKind::Let(_) = expr.kind {
- seen = true;
++ .is_some()
+}
+
+fn contains_let(cond: &Expr<'_>) -> bool {
-
- !seen
++ for_each_expr(cond, |e| {
++ if matches!(e.kind, ExprKind::Let(_)) {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(())
+ }
- .visit_expr(cond);
-
- seen
+ })
- &format!("declare `{}` here", binding_name),
++ .is_some()
+}
+
+fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
+ let StmtKind::Local(local) = stmt.kind else { return false };
+ !local.pat.walk_short(|pat| {
+ if let PatKind::Binding(.., None) = pat.kind {
+ !needs_ordered_drop(cx, cx.typeck_results().pat_ty(pat))
+ } else {
+ true
+ }
+ })
+}
+
+#[derive(Debug)]
+struct LocalAssign {
+ lhs_id: HirId,
+ lhs_span: Span,
+ rhs_span: Span,
+ span: Span,
+}
+
+impl LocalAssign {
+ fn from_expr(expr: &Expr<'_>, span: Span) -> Option<Self> {
+ if let ExprKind::Assign(lhs, rhs, _) = expr.kind {
+ if lhs.span.from_expansion() {
+ return None;
+ }
+
+ Some(Self {
+ lhs_id: path_to_local(lhs)?,
+ lhs_span: lhs.span,
+ rhs_span: rhs.span.source_callsite(),
+ span,
+ })
+ } else {
+ None
+ }
+ }
+
+ fn new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, binding_id: HirId) -> Option<LocalAssign> {
+ let assign = match expr.kind {
+ ExprKind::Block(Block { expr: Some(expr), .. }, _) => Self::from_expr(expr, expr.span),
+ ExprKind::Block(block, _) => {
+ if_chain! {
+ if let Some((last, other_stmts)) = block.stmts.split_last();
+ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = last.kind;
+
+ let assign = Self::from_expr(expr, last.span)?;
+
+ // avoid visiting if not needed
+ if assign.lhs_id == binding_id;
+ if other_stmts.iter().all(|stmt| !contains_assign_expr(cx, stmt));
+
+ then {
+ Some(assign)
+ } else {
+ None
+ }
+ }
+ },
+ ExprKind::Assign(..) => Self::from_expr(expr, expr.span),
+ _ => None,
+ }?;
+
+ if assign.lhs_id == binding_id {
+ Some(assign)
+ } else {
+ None
+ }
+ }
+}
+
+fn assignment_suggestions<'tcx>(
+ cx: &LateContext<'tcx>,
+ binding_id: HirId,
+ exprs: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
+) -> Option<(Applicability, Vec<(Span, String)>)> {
+ let mut assignments = Vec::new();
+
+ for expr in exprs {
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ if ty.is_never() {
+ continue;
+ }
+ if !ty.is_unit() {
+ return None;
+ }
+
+ let assign = LocalAssign::new(cx, expr, binding_id)?;
+
+ assignments.push(assign);
+ }
+
+ let suggestions = assignments
+ .iter()
+ .flat_map(|assignment| {
+ [
+ assignment.span.until(assignment.rhs_span),
+ assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()),
+ ]
+ })
+ .map(|span| (span, String::new()))
+ .collect::<Vec<(Span, String)>>();
+
+ match suggestions.len() {
+ // All of `exprs` are never types
+ // https://github.com/rust-lang/rust-clippy/issues/8911
+ 0 => None,
+ 1 => Some((Applicability::MachineApplicable, suggestions)),
+ // multiple suggestions don't work with rustfix in multipart_suggest
+ // https://github.com/rust-lang/rustfix/issues/141
+ _ => Some((Applicability::Unspecified, suggestions)),
+ }
+}
+
+struct Usage<'tcx> {
+ stmt: &'tcx Stmt<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ needs_semi: bool,
+}
+
+fn first_usage<'tcx>(
+ cx: &LateContext<'tcx>,
+ binding_id: HirId,
+ local_stmt_id: HirId,
+ block: &'tcx Block<'tcx>,
+) -> Option<Usage<'tcx>> {
+ let significant_drop = needs_ordered_drop(cx, cx.typeck_results().node_type(binding_id));
+
+ block
+ .stmts
+ .iter()
+ .skip_while(|stmt| stmt.hir_id != local_stmt_id)
+ .skip(1)
+ .take_while(|stmt| !significant_drop || !stmt_needs_ordered_drop(cx, stmt))
+ .find(|&stmt| is_local_used(cx, stmt, binding_id))
+ .and_then(|stmt| match stmt.kind {
+ StmtKind::Expr(expr) => Some(Usage {
+ stmt,
+ expr,
+ needs_semi: true,
+ }),
+ StmtKind::Semi(expr) => Some(Usage {
+ stmt,
+ expr,
+ needs_semi: false,
+ }),
+ _ => None,
+ })
+}
+
+fn local_snippet_without_semicolon(cx: &LateContext<'_>, local: &Local<'_>) -> Option<String> {
+ let span = local.span.with_hi(match local.ty {
+ // let <pat>: <ty>;
+ // ~~~~~~~~~~~~~~~
+ Some(ty) => ty.span.hi(),
+ // let <pat>;
+ // ~~~~~~~~~
+ None => local.pat.span.hi(),
+ });
+
+ snippet_opt(cx, span)
+}
+
+fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ local: &'tcx Local<'tcx>,
+ local_stmt: &'tcx Stmt<'tcx>,
+ block: &'tcx Block<'tcx>,
+ binding_id: HirId,
+) -> Option<()> {
+ let usage = first_usage(cx, binding_id, local_stmt.hir_id, block)?;
+ let binding_name = cx.tcx.hir().opt_name(binding_id)?;
+ let let_snippet = local_snippet_without_semicolon(cx, local)?;
+
+ match usage.expr.kind {
+ ExprKind::Assign(..) => {
+ let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
+ let mut msg_span = MultiSpan::from_spans(vec![local_stmt.span, assign.span]);
+ msg_span.push_span_label(local_stmt.span, "created here");
+ msg_span.push_span_label(assign.span, "initialised here");
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_LATE_INIT,
+ msg_span,
+ "unneeded late initialization",
+ |diag| {
+ diag.tool_only_span_suggestion(
+ local_stmt.span,
+ "remove the local",
+ "",
+ Applicability::MachineApplicable,
+ );
+
+ diag.span_suggestion(
+ assign.lhs_span,
- &format!("declare `{}` here", binding_name),
- format!("{} = ", let_snippet),
++ &format!("declare `{binding_name}` here"),
+ let_snippet,
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ },
+ ExprKind::If(cond, then_expr, Some(else_expr)) if !contains_let(cond) => {
+ let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_LATE_INIT,
+ local_stmt.span,
+ "unneeded late initialization",
+ |diag| {
+ diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
+
+ diag.span_suggestion_verbose(
+ usage.stmt.span.shrink_to_lo(),
- &format!("declare `{}` here", binding_name),
- format!("{} = ", let_snippet),
++ &format!("declare `{binding_name}` here"),
++ format!("{let_snippet} = "),
+ applicability,
+ );
+
+ diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability);
+
+ if usage.needs_semi {
+ diag.span_suggestion(
+ usage.stmt.span.shrink_to_hi(),
+ "add a semicolon after the `if` expression",
+ ";",
+ applicability,
+ );
+ }
+ },
+ );
+ },
+ ExprKind::Match(_, arms, MatchSource::Normal) => {
+ let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_LATE_INIT,
+ local_stmt.span,
+ "unneeded late initialization",
+ |diag| {
+ diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
+
+ diag.span_suggestion_verbose(
+ usage.stmt.span.shrink_to_lo(),
++ &format!("declare `{binding_name}` here"),
++ format!("{let_snippet} = "),
+ applicability,
+ );
+
+ diag.multipart_suggestion(
+ "remove the assignments from the `match` arms",
+ suggestions,
+ applicability,
+ );
+
+ if usage.needs_semi {
+ diag.span_suggestion(
+ usage.stmt.span.shrink_to_hi(),
+ "add a semicolon after the `match` expression",
+ ";",
+ applicability,
+ );
+ }
+ },
+ );
+ },
+ _ => {},
+ };
+
+ Some(())
+}
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit {
+ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
+ let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
+ if_chain! {
+ if let Local {
+ init: None,
+ pat: &Pat {
+ kind: PatKind::Binding(BindingAnnotation::NONE, binding_id, _, None),
+ ..
+ },
+ source: LocalSource::Normal,
+ ..
+ } = local;
+ if let Some((_, Node::Stmt(local_stmt))) = parents.next();
+ if let Some((_, Node::Block(block))) = parents.next();
+
+ then {
+ check(cx, local, local_stmt, block, binding_id);
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_span::{sym, Span};
+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};
+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};
++use rustc_hir_analysis::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_hir_analysis::expr_use_visitor as euv;
++use rustc_span::{sym, Span, DUMMY_SP};
+use rustc_target::spec::abi::Abi;
+use rustc_trait_selection::traits;
+use rustc_trait_selection::traits::misc::can_type_implement_copy;
- |x| Cow::from(format!("change `{}` to", x)),
+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();
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ 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.at(DUMMY_SP), 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 `{}` to", x))
++ |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_diagnostic_item(cx, ty, sym::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(),
- fn fake_read(&mut self, _: &rustc_hir_analysis::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
++ |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_analysis::expr_use_visitor::PlaceWithHirId<'tcx>,
++ _: FakeReadCause,
++ _: HirId,
++ ) {
++ }
+}
--- /dev/null
- use clippy_utils::is_lang_ctor;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- use rustc_hir::LangItem::{OptionSome, ResultOk};
++use clippy_utils::path_res;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- if let ExprKind::Call(path, [arg]) = &expr.kind;
- if let ExprKind::Path(ref qpath) = &path.kind;
- let sugg_remove = if is_lang_ctor(cx, qpath, OptionSome) {
++use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath};
+use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty::DefIdTree;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests alternatives for useless applications of `?` in terminating expressions
+ ///
+ /// ### Why is this bad?
+ /// There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct TO {
+ /// magic: Option<usize>,
+ /// }
+ ///
+ /// fn f(to: TO) -> Option<usize> {
+ /// Some(to.magic?)
+ /// }
+ ///
+ /// struct TR {
+ /// magic: Result<usize, bool>,
+ /// }
+ ///
+ /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
+ /// tr.and_then(|t| Ok(t.magic?))
+ /// }
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct TO {
+ /// magic: Option<usize>,
+ /// }
+ ///
+ /// fn f(to: TO) -> Option<usize> {
+ /// to.magic
+ /// }
+ ///
+ /// struct TR {
+ /// magic: Result<usize, bool>,
+ /// }
+ ///
+ /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
+ /// tr.and_then(|t| t.magic)
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub NEEDLESS_QUESTION_MARK,
+ complexity,
+ "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result<T, E>`."
+}
+
+declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
+
+impl LateLintPass<'_> for NeedlessQuestionMark {
+ /*
+ * The question mark operator is compatible with both Result<T, E> and Option<T>,
+ * from Rust 1.13 and 1.22 respectively.
+ */
+
+ /*
+ * What do we match:
+ * Expressions that look like this:
+ * Some(option?), Ok(result?)
+ *
+ * Where do we match:
+ * Last expression of a body
+ * Return statement
+ * A body's value (single line closure)
+ *
+ * What do we not match:
+ * Implicit calls to `from(..)` on the error value
+ */
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
+ if let ExprKind::Ret(Some(e)) = expr.kind {
+ check(cx, e);
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
+ if let Some(GeneratorKind::Async(AsyncGeneratorKind::Fn)) = body.generator_kind {
+ if let ExprKind::Block(
+ Block {
+ expr:
+ Some(Expr {
+ kind: ExprKind::DropTemps(async_body),
+ ..
+ }),
+ ..
+ },
+ _,
+ ) = body.value.kind
+ {
+ if let ExprKind::Block(Block { expr: Some(expr), .. }, ..) = async_body.kind {
+ check(cx, expr);
+ }
+ }
+ } else {
+ check(cx, body.value.peel_blocks());
+ }
+ }
+}
+
+fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
- } else if is_lang_ctor(cx, qpath, ResultOk) {
++ if let ExprKind::Call(path, [arg]) = expr.kind;
++ if let Res::Def(DefKind::Ctor(..), ctor_id) = path_res(cx, path);
++ if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
++ let sugg_remove = if cx.tcx.lang_items().option_some_variant() == Some(variant_id) {
+ "Some()"
- &format!("try removing question mark and `{}`", sugg_remove),
++ } else if cx.tcx.lang_items().result_ok_variant() == Some(variant_id) {
+ "Ok()"
+ } else {
+ return;
+ };
+ if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind;
+ if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
+ if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)) = &called.kind;
+ if expr.span.ctxt() == inner_expr.span.ctxt();
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ let inner_ty = cx.typeck_results().expr_ty(inner_expr);
+ if expr_ty == inner_ty;
+ then {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_QUESTION_MARK,
+ expr.span,
+ "question mark operator is useless here",
++ &format!("try removing question mark and `{sugg_remove}`"),
+ format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- format!("-({})", snip)
+use clippy_utils::consts::{self, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::has_enclosing_paren;
+use if_chain::if_chain;
+use rustc_ast::util::parser::PREC_PREFIX;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for multiplication by -1 as a form of negation.
+ ///
+ /// ### Why is this bad?
+ /// It's more readable to just negate.
+ ///
+ /// ### Known problems
+ /// This only catches integers (for now).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let a = x * -1;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// let a = -x;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEG_MULTIPLY,
+ style,
+ "multiplying integers by `-1`"
+}
+
+declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]);
+
+impl<'tcx> LateLintPass<'tcx> for NegMultiply {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, left, right) = e.kind {
+ if BinOpKind::Mul == op.node {
+ match (&left.kind, &right.kind) {
+ (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {},
+ (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right),
+ (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left),
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Lit(ref l) = lit.kind;
+ if consts::lit_to_mir_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)) == Constant::Int(1);
+ if cx.typeck_results().expr_ty(exp).is_integral();
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let snip = snippet_with_applicability(cx, exp.span, "..", &mut applicability);
+ let suggestion = if exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) {
- format!("-{}", snip)
++ format!("-({snip})")
+ } else {
++ format!("-{snip}")
+ };
+ span_lint_and_sugg(
+ cx,
+ NEG_MULTIPLY,
+ span,
+ "this multiplication by -1 can be written more succinctly",
+ "consider using",
+ suggestion,
+ applicability,
+ );
+ }
+ }
+}
--- /dev/null
- "you should consider adding a `Default` implementation for `{}`",
- self_type_snip
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::return_ty;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::DiagnosticExt;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::HirIdSet;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public types with a `pub fn new() -> Self` method and no
+ /// implementation of
+ /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
+ ///
+ /// ### Why is this bad?
+ /// The user might expect to be able to use
+ /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
+ /// type can be constructed without arguments.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// pub struct Foo(Bar);
+ ///
+ /// impl Foo {
+ /// pub fn new() -> Self {
+ /// Foo(Bar::new())
+ /// }
+ /// }
+ /// ```
+ ///
+ /// To fix the lint, add a `Default` implementation that delegates to `new`:
+ ///
+ /// ```ignore
+ /// pub struct Foo(Bar);
+ ///
+ /// impl Default for Foo {
+ /// fn default() -> Self {
+ /// Foo::new()
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEW_WITHOUT_DEFAULT,
+ style,
+ "`pub fn new() -> Self` method without `Default` implementation"
+}
+
+#[derive(Clone, Default)]
+pub struct NewWithoutDefault {
+ impling_types: Option<HirIdSet>,
+}
+
+impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]);
+
+impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if let hir::ItemKind::Impl(hir::Impl {
+ of_trait: None,
+ generics,
+ self_ty: impl_self_ty,
+ items,
+ ..
+ }) = item.kind
+ {
+ for assoc_item in *items {
+ if assoc_item.kind == (hir::AssocItemKind::Fn { has_self: false }) {
+ let impl_item = cx.tcx.hir().impl_item(assoc_item.id);
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind {
+ let name = impl_item.ident.name;
+ let id = impl_item.hir_id();
+ if sig.header.constness == hir::Constness::Const {
+ // can't be implemented by default
+ return;
+ }
+ if sig.header.unsafety == hir::Unsafety::Unsafe {
+ // can't be implemented for unsafe new
+ return;
+ }
+ if cx.tcx.is_doc_hidden(impl_item.def_id.def_id) {
+ // shouldn't be implemented when it is hidden in docs
+ return;
+ }
+ if !impl_item.generics.params.is_empty() {
+ // when the result of `new()` depends on a parameter we should not require
+ // an impl of `Default`
+ return;
+ }
+ if_chain! {
+ if sig.decl.inputs.is_empty();
+ if name == sym::new;
+ if cx.access_levels.is_reachable(impl_item.def_id.def_id);
+ let self_def_id = cx.tcx.hir().get_parent_item(id);
+ let self_ty = cx.tcx.type_of(self_def_id);
+ if self_ty == return_ty(cx, id);
+ if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
+ then {
+ if self.impling_types.is_none() {
+ let mut impls = HirIdSet::default();
+ cx.tcx.for_each_impl(default_trait_id, |d| {
+ if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
+ if let Some(local_def_id) = ty_def.did().as_local() {
+ impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
+ }
+ }
+ });
+ self.impling_types = Some(impls);
+ }
+
+ // Check if a Default implementation exists for the Self type, regardless of
+ // generics
+ if_chain! {
+ if let Some(ref impling_types) = self.impling_types;
+ if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
+ if let Some(self_local_did) = self_def.did().as_local();
+ let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
+ if impling_types.contains(&self_id);
+ then {
+ return;
+ }
+ }
+
+ let generics_sugg = snippet(cx, generics.span, "");
+ let self_ty_fmt = self_ty.to_string();
+ let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt);
+ span_lint_hir_and_then(
+ cx,
+ NEW_WITHOUT_DEFAULT,
+ id,
+ impl_item.span,
+ &format!(
- "impl{} Default for {} {{
++ "you should consider adding a `Default` implementation for `{self_type_snip}`"
+ ),
+ |diag| {
+ diag.suggest_prepend_item(
+ cx,
+ item.span,
+ "try adding this",
+ &create_new_without_default_suggest_msg(&self_type_snip, &generics_sugg),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn create_new_without_default_suggest_msg(self_type_snip: &str, generics_sugg: &str) -> String {
+ #[rustfmt::skip]
+ format!(
- }}", generics_sugg, self_type_snip)
++"impl{generics_sugg} Default for {self_type_snip} {{
+ fn default() -> Self {{
+ Self::new()
+ }}
++}}")
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+//! Checks for uses of const which the type is not `Freeze` (`Cell`-free).
+//!
+//! This lint is **warn** by default.
+
+use std::ptr;
+
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::in_constant;
+use clippy_utils::macros::macro_backtrace;
+use if_chain::if_chain;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{
+ BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp,
+};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass, Lint};
+use rustc_middle::mir;
+use rustc_middle::mir::interpret::{ConstValue, ErrorHandled};
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, InnerSpan, Span, DUMMY_SP};
+
+// FIXME: this is a correctness problem but there's no suitable
+// warn-by-default category.
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for declaration of `const` items which is interior
+ /// mutable (e.g., contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.).
+ ///
+ /// ### Why is this bad?
+ /// Consts are copied everywhere they are referenced, i.e.,
+ /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
+ /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
+ /// these types in the first place.
+ ///
+ /// The `const` should better be replaced by a `static` item if a global
+ /// variable is wanted, or replaced by a `const fn` if a constructor is wanted.
+ ///
+ /// ### Known problems
+ /// A "non-constant" const item is a legacy way to supply an
+ /// initialized value to downstream `static` items (e.g., the
+ /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit,
+ /// and this lint should be suppressed.
+ ///
+ /// Even though the lint avoids triggering on a constant whose type has enums that have variants
+ /// with interior mutability, and its value uses non interior mutable variants (see
+ /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962) and
+ /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825) for examples);
+ /// it complains about associated constants without default values only based on its types;
+ /// which might not be preferable.
+ /// There're other enums plus associated constants cases that the lint cannot handle.
+ ///
+ /// Types that have underlying or potential interior mutability trigger the lint whether
+ /// the interior mutable field is used or not. See issues
+ /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ ///
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
+ /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15);
+ /// STATIC_ATOM.store(9, SeqCst);
+ /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DECLARE_INTERIOR_MUTABLE_CONST,
+ style,
+ "declaring `const` with interior mutability"
+}
+
+// FIXME: this is a correctness problem but there's no suitable
+// warn-by-default category.
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks if `const` items which is interior mutable (e.g.,
+ /// contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly.
+ ///
+ /// ### Why is this bad?
+ /// Consts are copied everywhere they are referenced, i.e.,
+ /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
+ /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
+ /// these types in the first place.
+ ///
+ /// The `const` value should be stored inside a `static` item.
+ ///
+ /// ### Known problems
+ /// When an enum has variants with interior mutability, use of its non
+ /// interior mutable variants can generate false positives. See issue
+ /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962)
+ ///
+ /// Types that have underlying or potential interior mutability trigger the lint whether
+ /// the interior mutable field is used or not. See issues
+ /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and
+ /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825)
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ ///
+ /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
+ /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ ///
+ /// static STATIC_ATOM: AtomicUsize = CONST_ATOM;
+ /// STATIC_ATOM.store(9, SeqCst);
+ /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub BORROW_INTERIOR_MUTABLE_CONST,
+ style,
+ "referencing `const` with interior mutability"
+}
+
+fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ // Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
+ // making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
+ // 'unfrozen'. However, this code causes a false negative in which
+ // a type contains a layout-unknown type, but also an unsafe cell like `const CELL: Cell<T>`.
+ // Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
+ // since it works when a pointer indirection involves (`Cell<*const T>`).
+ // Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
+ // but I'm not sure whether it's a decent way, if possible.
+ cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx.at(DUMMY_SP), cx.param_env)
+}
+
+fn is_value_unfrozen_raw<'tcx>(
+ cx: &LateContext<'tcx>,
+ result: Result<ConstValue<'tcx>, ErrorHandled>,
+ ty: Ty<'tcx>,
+) -> bool {
+ fn inner<'tcx>(cx: &LateContext<'tcx>, val: mir::ConstantKind<'tcx>) -> bool {
+ match val.ty().kind() {
+ // the fact that we have to dig into every structs to search enums
+ // leads us to the point checking `UnsafeCell` directly is the only option.
+ ty::Adt(ty_def, ..) if ty_def.is_unsafe_cell() => true,
++ // As of 2022-09-08 miri doesn't track which union field is active so there's no safe way to check the
++ // contained value.
++ ty::Adt(def, ..) if def.is_union() => false,
+ ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => {
+ let val = cx.tcx.destructure_mir_constant(cx.param_env, val);
+ val.fields.iter().any(|field| inner(cx, *field))
+ },
+ _ => false,
+ }
+ }
+ result.map_or_else(
+ |err| {
+ // Consider `TooGeneric` cases as being unfrozen.
+ // This causes a false positive where an assoc const whose type is unfrozen
+ // have a value that is a frozen variant with a generic param (an example is
+ // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
+ // However, it prevents a number of false negatives that is, I think, important:
+ // 1. assoc consts in trait defs referring to consts of themselves
+ // (an example is `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
+ // 2. a path expr referring to assoc consts whose type is doesn't have
+ // any frozen variants in trait defs (i.e. without substitute for `Self`).
+ // (e.g. borrowing `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
+ // 3. similar to the false positive above;
+ // but the value is an unfrozen variant, or the type has no enums. (An example is
+ // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT`
+ // and `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
+ // One might be able to prevent these FNs correctly, and replace this with `false`;
+ // e.g. implementing `has_frozen_variant` described above, and not running this function
+ // when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
+ // case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
+ // similar to 2., but with the a frozen variant) (e.g. borrowing
+ // `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
+ // I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
+ err == ErrorHandled::TooGeneric
+ },
+ |val| inner(cx, mir::ConstantKind::from_value(val, ty)),
+ )
+}
+
+fn is_value_unfrozen_poly<'tcx>(cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool {
+ let result = cx.tcx.const_eval_poly(body_id.hir_id.owner.to_def_id());
+ is_value_unfrozen_raw(cx, result, ty)
+}
+
+fn is_value_unfrozen_expr<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
+ let substs = cx.typeck_results().node_substs(hir_id);
+
+ let result = cx.tcx.const_eval_resolve(
+ cx.param_env,
+ mir::UnevaluatedConst::new(ty::WithOptConstParam::unknown(def_id), substs),
+ None,
+ );
+ is_value_unfrozen_raw(cx, result, ty)
+}
+
+#[derive(Copy, Clone)]
+enum Source {
+ Item { item: Span },
+ Assoc { item: Span },
+ Expr { expr: Span },
+}
+
+impl Source {
+ #[must_use]
+ fn lint(&self) -> (&'static Lint, &'static str, Span) {
+ match self {
+ Self::Item { item } | Self::Assoc { item, .. } => (
+ DECLARE_INTERIOR_MUTABLE_CONST,
+ "a `const` item should never be interior mutable",
+ *item,
+ ),
+ Self::Expr { expr } => (
+ BORROW_INTERIOR_MUTABLE_CONST,
+ "a `const` item with interior mutability should not be borrowed",
+ *expr,
+ ),
+ }
+ }
+}
+
+fn lint(cx: &LateContext<'_>, source: Source) {
+ let (lint, msg, span) = source.lint();
+ span_lint_and_then(cx, lint, span, msg, |diag| {
+ if span.from_expansion() {
+ return; // Don't give suggestions into macros.
+ }
+ match source {
+ Source::Item { .. } => {
+ let const_kw_span = span.from_inner(InnerSpan::new(0, 5));
+ diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)");
+ },
+ Source::Assoc { .. } => (),
+ Source::Expr { .. } => {
+ diag.help("assign this const to a local or static variable, and use the variable here");
+ },
+ }
+ });
+}
+
+declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);
+
+impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
+ if let ItemKind::Const(hir_ty, body_id) = it.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
+ lint(cx, Source::Item { item: it.span });
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Const(hir_ty, body_id_opt) = &trait_item.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+
+ // Normalize assoc types because ones originated from generic params
+ // bounded other traits could have their bound.
+ let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
+ if is_unfrozen(cx, normalized)
+ // When there's no default value, lint it only according to its type;
+ // in other words, lint consts whose value *could* be unfrozen, not definitely is.
+ // This feels inconsistent with how the lint treats generic types,
+ // which avoids linting types which potentially become unfrozen.
+ // One could check whether an unfrozen type have a *frozen variant*
+ // (like `body_id_opt.map_or_else(|| !has_frozen_variant(...), ...)`),
+ // and do the same as the case of generic types at impl items.
+ // Note that it isn't sufficient to check if it has an enum
+ // since all of that enum's variants can be unfrozen:
+ // i.e. having an enum doesn't necessary mean a type has a frozen variant.
+ // And, implementing it isn't a trivial task; it'll probably end up
+ // re-implementing the trait predicate evaluation specific to `Freeze`.
+ && body_id_opt.map_or(true, |body_id| is_value_unfrozen_poly(cx, body_id, normalized))
+ {
+ lint(cx, Source::Assoc { item: trait_item.span });
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ if let ImplItemKind::Const(hir_ty, body_id) = &impl_item.kind {
+ let item_def_id = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id;
+ let item = cx.tcx.hir().expect_item(item_def_id);
+
+ match &item.kind {
+ ItemKind::Impl(Impl {
+ of_trait: Some(of_trait_ref),
+ ..
+ }) => {
+ if_chain! {
+ // Lint a trait impl item only when the definition is a generic type,
+ // assuming an assoc const is not meant to be an interior mutable type.
+ if let Some(of_trait_def_id) = of_trait_ref.trait_def_id();
+ if let Some(of_assoc_item) = cx
+ .tcx
+ .associated_item(impl_item.def_id)
+ .trait_item_def_id;
+ if cx
+ .tcx
+ .layout_of(cx.tcx.param_env(of_trait_def_id).and(
+ // Normalize assoc types because ones originated from generic params
+ // bounded other traits could have their bound at the trait defs;
+ // and, in that case, the definition is *not* generic.
+ cx.tcx.normalize_erasing_regions(
+ cx.tcx.param_env(of_trait_def_id),
+ cx.tcx.type_of(of_assoc_item),
+ ),
+ ))
+ .is_err();
+ // If there were a function like `has_frozen_variant` described above,
+ // we should use here as a frozen variant is a potential to be frozen
+ // similar to unknown layouts.
+ // e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
+ if is_unfrozen(cx, normalized);
+ if is_value_unfrozen_poly(cx, *body_id, normalized);
+ then {
+ lint(
+ cx,
+ Source::Assoc {
+ item: impl_item.span,
+ },
+ );
+ }
+ }
+ },
+ ItemKind::Impl(Impl { of_trait: None, .. }) => {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ // Normalize assoc types originated from generic params.
+ let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
+
+ if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, *body_id, normalized) {
+ lint(cx, Source::Assoc { item: impl_item.span });
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Path(qpath) = &expr.kind {
+ // Only lint if we use the const item inside a function.
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ // Make sure it is a const item.
+ let item_def_id = match cx.qpath_res(qpath, expr.hir_id) {
+ Res::Def(DefKind::Const | DefKind::AssocConst, did) => did,
+ _ => return,
+ };
+
+ // Climb up to resolve any field access and explicit referencing.
+ let mut cur_expr = expr;
+ let mut dereferenced_expr = expr;
+ let mut needs_check_adjustment = true;
+ loop {
+ let parent_id = cx.tcx.hir().get_parent_node(cur_expr.hir_id);
+ if parent_id == cur_expr.hir_id {
+ break;
+ }
+ if let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find(parent_id) {
+ match &parent_expr.kind {
+ ExprKind::AddrOf(..) => {
+ // `&e` => `e` must be referenced.
+ needs_check_adjustment = false;
+ },
+ ExprKind::Field(..) => {
+ needs_check_adjustment = true;
+
+ // Check whether implicit dereferences happened;
+ // if so, no need to go further up
+ // because of the same reason as the `ExprKind::Unary` case.
+ if cx
+ .typeck_results()
+ .expr_adjustments(dereferenced_expr)
+ .iter()
+ .any(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ {
+ break;
+ }
+
+ dereferenced_expr = parent_expr;
+ },
+ ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => {
+ // `e[i]` => desugared to `*Index::index(&e, i)`,
+ // meaning `e` must be referenced.
+ // no need to go further up since a method call is involved now.
+ needs_check_adjustment = false;
+ break;
+ },
+ ExprKind::Unary(UnOp::Deref, _) => {
+ // `*e` => desugared to `*Deref::deref(&e)`,
+ // meaning `e` must be referenced.
+ // no need to go further up since a method call is involved now.
+ needs_check_adjustment = false;
+ break;
+ },
+ _ => break,
+ }
+ cur_expr = parent_expr;
+ } else {
+ break;
+ }
+ }
+
+ let ty = if needs_check_adjustment {
+ let adjustments = cx.typeck_results().expr_adjustments(dereferenced_expr);
+ if let Some(i) = adjustments
+ .iter()
+ .position(|adj| matches!(adj.kind, Adjust::Borrow(_) | Adjust::Deref(_)))
+ {
+ if i == 0 {
+ cx.typeck_results().expr_ty(dereferenced_expr)
+ } else {
+ adjustments[i - 1].target
+ }
+ } else {
+ // No borrow adjustments means the entire const is moved.
+ return;
+ }
+ } else {
+ cx.typeck_results().expr_ty(dereferenced_expr)
+ };
+
+ if is_unfrozen(cx, ty) && is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty) {
+ lint(cx, Source::Expr { expr: expr.span });
+ }
+ }
+ }
+}
+
+fn ignored_macro(cx: &LateContext<'_>, it: &rustc_hir::Item<'_>) -> bool {
+ macro_backtrace(it.span).any(|macro_call| {
+ matches!(
+ cx.tcx.get_diagnostic_name(macro_call.def_id),
+ Some(sym::thread_local_macro)
+ )
+ })
+}
--- /dev/null
- &format!(
- "{} bindings with single-character names in scope",
- num_single_char_names
- ),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use rustc_ast::ast::{
+ self, Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, Item, ItemKind, Local, Pat, PatKind,
+};
+use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor};
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+use rustc_span::symbol::{Ident, Symbol};
+use std::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for names that are very similar and thus confusing.
+ ///
+ /// Note: this lint looks for similar names throughout each
+ /// scope. To allow it, you need to allow it on the scope
+ /// level, not on the name that is reported.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to distinguish between names that differ only
+ /// by a single character.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let checked_exp = something;
+ /// let checked_expr = something_else;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SIMILAR_NAMES,
+ pedantic,
+ "similarly named items and bindings"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for too many variables whose name consists of a
+ /// single character.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to memorize what a variable means without a
+ /// descriptive name.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let (a, b, c, d, e, f, g) = (...);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MANY_SINGLE_CHAR_NAMES,
+ pedantic,
+ "too many single character bindings"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks if you have variables whose name consists of just
+ /// underscores and digits.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to memorize what a variable means without a
+ /// descriptive name.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _1 = 1;
+ /// let ___1 = 1;
+ /// let __1___2 = 11;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub JUST_UNDERSCORES_AND_DIGITS,
+ style,
+ "unclear name"
+}
+
+#[derive(Copy, Clone)]
+pub struct NonExpressiveNames {
+ pub single_char_binding_names_threshold: u64,
+}
+
+impl_lint_pass!(NonExpressiveNames => [SIMILAR_NAMES, MANY_SINGLE_CHAR_NAMES, JUST_UNDERSCORES_AND_DIGITS]);
+
+struct ExistingName {
+ interned: Symbol,
+ span: Span,
+ len: usize,
+ exemptions: &'static [&'static str],
+}
+
+struct SimilarNamesLocalVisitor<'a, 'tcx> {
+ names: Vec<ExistingName>,
+ cx: &'a EarlyContext<'tcx>,
+ lint: &'a NonExpressiveNames,
+
+ /// A stack of scopes containing the single-character bindings in each scope.
+ single_char_names: Vec<Vec<Ident>>,
+}
+
+impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
+ fn check_single_char_names(&self) {
+ let num_single_char_names = self.single_char_names.iter().flatten().count();
+ let threshold = self.lint.single_char_binding_names_threshold;
+ if num_single_char_names as u64 > threshold {
+ let span = self
+ .single_char_names
+ .iter()
+ .flatten()
+ .map(|ident| ident.span)
+ .collect::<Vec<_>>();
+ span_lint(
+ self.cx,
+ MANY_SINGLE_CHAR_NAMES,
+ span,
++ &format!("{num_single_char_names} bindings with single-character names in scope"),
+ );
+ }
+ }
+}
+
+// this list contains lists of names that are allowed to be similar
+// the assumption is that no name is ever contained in multiple lists.
+#[rustfmt::skip]
+const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
+ &["parsed", "parser"],
+ &["lhs", "rhs"],
+ &["tx", "rx"],
+ &["set", "get"],
+ &["args", "arms"],
+ &["qpath", "path"],
+ &["lit", "lint"],
+ &["wparam", "lparam"],
+ &["iter", "item"],
+];
+
+struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>);
+
+impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
+ fn visit_pat(&mut self, pat: &'tcx Pat) {
+ match pat.kind {
+ PatKind::Ident(_, ident, _) => {
+ if !pat.span.from_expansion() {
+ self.check_ident(ident);
+ }
+ },
+ PatKind::Struct(_, _, ref fields, _) => {
+ for field in fields {
+ if !field.is_shorthand {
+ self.visit_pat(&field.pat);
+ }
+ }
+ },
+ // just go through the first pattern, as either all patterns
+ // bind the same bindings or rustc would have errored much earlier
+ PatKind::Or(ref pats) => self.visit_pat(&pats[0]),
+ _ => walk_pat(self, pat),
+ }
+ }
+}
+
+#[must_use]
+fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
+ ALLOWED_TO_BE_SIMILAR
+ .iter()
+ .find(|&&list| allowed_to_be_similar(interned_name, list))
+ .copied()
+}
+
+#[must_use]
+fn allowed_to_be_similar(interned_name: &str, list: &[&str]) -> bool {
+ list.iter()
+ .any(|&name| interned_name.starts_with(name) || interned_name.ends_with(name))
+}
+
+impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
+ fn check_short_ident(&mut self, ident: Ident) {
+ // Ignore shadowing
+ if self
+ .0
+ .single_char_names
+ .iter()
+ .flatten()
+ .any(|id| id.name == ident.name)
+ {
+ return;
+ }
+
+ if let Some(scope) = &mut self.0.single_char_names.last_mut() {
+ scope.push(ident);
+ }
+ }
+
+ #[expect(clippy::too_many_lines)]
+ fn check_ident(&mut self, ident: Ident) {
+ let interned_name = ident.name.as_str();
+ if interned_name.chars().any(char::is_uppercase) {
+ return;
+ }
+ if interned_name.chars().all(|c| c.is_ascii_digit() || c == '_') {
+ span_lint(
+ self.0.cx,
+ JUST_UNDERSCORES_AND_DIGITS,
+ ident.span,
+ "consider choosing a more descriptive name",
+ );
+ return;
+ }
+ if interned_name.starts_with('_') {
+ // these bindings are typically unused or represent an ignored portion of a destructuring pattern
+ return;
+ }
+ let count = interned_name.chars().count();
+ if count < 3 {
+ if count == 1 {
+ self.check_short_ident(ident);
+ }
+ return;
+ }
+ for existing_name in &self.0.names {
+ if allowed_to_be_similar(interned_name, existing_name.exemptions) {
+ continue;
+ }
+ match existing_name.len.cmp(&count) {
+ Ordering::Greater => {
+ if existing_name.len - count != 1
+ || levenstein_not_1(interned_name, existing_name.interned.as_str())
+ {
+ continue;
+ }
+ },
+ Ordering::Less => {
+ if count - existing_name.len != 1
+ || levenstein_not_1(existing_name.interned.as_str(), interned_name)
+ {
+ continue;
+ }
+ },
+ Ordering::Equal => {
+ let mut interned_chars = interned_name.chars();
+ let interned_str = existing_name.interned.as_str();
+ let mut existing_chars = interned_str.chars();
+ let first_i = interned_chars.next().expect("we know we have at least one char");
+ let first_e = existing_chars.next().expect("we know we have at least one char");
+ let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
+
+ if eq_or_numeric((first_i, first_e)) {
+ let last_i = interned_chars.next_back().expect("we know we have at least two chars");
+ let last_e = existing_chars.next_back().expect("we know we have at least two chars");
+ if eq_or_numeric((last_i, last_e)) {
+ if interned_chars
+ .zip(existing_chars)
+ .filter(|&ie| !eq_or_numeric(ie))
+ .count()
+ != 1
+ {
+ continue;
+ }
+ } else {
+ let second_last_i = interned_chars
+ .next_back()
+ .expect("we know we have at least three chars");
+ let second_last_e = existing_chars
+ .next_back()
+ .expect("we know we have at least three chars");
+ if !eq_or_numeric((second_last_i, second_last_e))
+ || second_last_i == '_'
+ || !interned_chars.zip(existing_chars).all(eq_or_numeric)
+ {
+ // allowed similarity foo_x, foo_y
+ // or too many chars differ (foo_x, boo_y) or (foox, booy)
+ continue;
+ }
+ }
+ } else {
+ let second_i = interned_chars.next().expect("we know we have at least two chars");
+ let second_e = existing_chars.next().expect("we know we have at least two chars");
+ if !eq_or_numeric((second_i, second_e))
+ || second_i == '_'
+ || !interned_chars.zip(existing_chars).all(eq_or_numeric)
+ {
+ // allowed similarity x_foo, y_foo
+ // or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
+ continue;
+ }
+ }
+ },
+ }
+ span_lint_and_then(
+ self.0.cx,
+ SIMILAR_NAMES,
+ ident.span,
+ "binding's name is too similar to existing binding",
+ |diag| {
+ diag.span_note(existing_name.span, "existing binding defined here");
+ },
+ );
+ return;
+ }
+ self.0.names.push(ExistingName {
+ exemptions: get_exemptions(interned_name).unwrap_or(&[]),
+ interned: ident.name,
+ span: ident.span,
+ len: count,
+ });
+ }
+}
+
+impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {
+ /// ensure scoping rules work
+ fn apply<F: for<'c> Fn(&'c mut Self)>(&mut self, f: F) {
+ let n = self.names.len();
+ let single_char_count = self.single_char_names.len();
+ f(self);
+ self.names.truncate(n);
+ self.single_char_names.truncate(single_char_count);
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
+ fn visit_local(&mut self, local: &'tcx Local) {
+ if let Some((init, els)) = &local.kind.init_else_opt() {
+ self.apply(|this| walk_expr(this, init));
+ if let Some(els) = els {
+ self.apply(|this| walk_block(this, els));
+ }
+ }
+ // add the pattern after the expression because the bindings aren't available
+ // yet in the init
+ // expression
+ SimilarNamesNameVisitor(self).visit_pat(&local.pat);
+ }
+ fn visit_block(&mut self, blk: &'tcx Block) {
+ self.single_char_names.push(vec![]);
+
+ self.apply(|this| walk_block(this, blk));
+
+ self.check_single_char_names();
+ self.single_char_names.pop();
+ }
+ fn visit_arm(&mut self, arm: &'tcx Arm) {
+ self.single_char_names.push(vec![]);
+
+ self.apply(|this| {
+ SimilarNamesNameVisitor(this).visit_pat(&arm.pat);
+ this.apply(|this| walk_expr(this, &arm.body));
+ });
+
+ self.check_single_char_names();
+ self.single_char_names.pop();
+ }
+ fn visit_item(&mut self, _: &Item) {
+ // do not recurse into inner items
+ }
+}
+
+impl EarlyLintPass for NonExpressiveNames {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if in_external_macro(cx.sess(), item.span) {
+ return;
+ }
+
+ if let ItemKind::Fn(box ast::Fn {
+ ref sig,
+ body: Some(ref blk),
+ ..
+ }) = item.kind
+ {
+ do_check(self, cx, &item.attrs, &sig.decl, blk);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &AssocItem) {
+ if in_external_macro(cx.sess(), item.span) {
+ return;
+ }
+
+ if let AssocItemKind::Fn(box ast::Fn {
+ ref sig,
+ body: Some(ref blk),
+ ..
+ }) = item.kind
+ {
+ do_check(self, cx, &item.attrs, &sig.decl, blk);
+ }
+ }
+}
+
+fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) {
+ if !attrs.iter().any(|attr| attr.has_name(sym::test)) {
+ let mut visitor = SimilarNamesLocalVisitor {
+ names: Vec::new(),
+ cx,
+ lint,
+ single_char_names: vec![vec![]],
+ };
+
+ // initialize with function arguments
+ for arg in &decl.inputs {
+ SimilarNamesNameVisitor(&mut visitor).visit_pat(&arg.pat);
+ }
+ // walk all other bindings
+ walk_block(&mut visitor, blk);
+
+ visitor.check_single_char_names();
+ }
+}
+
+/// Precondition: `a_name.chars().count() < b_name.chars().count()`.
+#[must_use]
+fn levenstein_not_1(a_name: &str, b_name: &str) -> bool {
+ debug_assert!(a_name.chars().count() < b_name.chars().count());
+ let mut a_chars = a_name.chars();
+ let mut b_chars = b_name.chars();
+ while let (Some(a), Some(b)) = (a_chars.next(), b_chars.next()) {
+ if a == b {
+ continue;
+ }
+ if let Some(b2) = b_chars.next() {
+ // check if there's just one character inserted
+ return a != b2 || a_chars.ne(b_chars);
+ }
+ // tuple
+ // ntuple
+ return true;
+ }
+ // for item in items
+ true
+}
--- /dev/null
- use clippy_utils::ty::match_type;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
- || match_type(cx, obj_ty, &paths::DIR_BUILDER)))
++use clippy_utils::ty::{is_type_diagnostic_item, match_type};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for non-octal values used to set Unix file permissions.
+ ///
+ /// ### Why is this bad?
+ /// They will be converted into octal, creating potentially
+ /// unintended file permissions.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::fs::OpenOptions;
+ /// use std::os::unix::fs::OpenOptionsExt;
+ ///
+ /// let mut options = OpenOptions::new();
+ /// options.mode(644);
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// use std::fs::OpenOptions;
+ /// use std::os::unix::fs::OpenOptionsExt;
+ ///
+ /// let mut options = OpenOptions::new();
+ /// options.mode(0o644);
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub NON_OCTAL_UNIX_PERMISSIONS,
+ correctness,
+ "use of non-octal value to set unix file permissions, which will be translated into octal"
+}
+
+declare_lint_pass!(NonOctalUnixPermissions => [NON_OCTAL_UNIX_PERMISSIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ match &expr.kind {
+ ExprKind::MethodCall(path, func, [param], _) => {
+ let obj_ty = cx.typeck_results().expr_ty(func).peel_refs();
+
+ if_chain! {
+ if (path.ident.name == sym!(mode)
+ && (match_type(cx, obj_ty, &paths::OPEN_OPTIONS)
++ || is_type_diagnostic_item(cx, obj_ty, sym::DirBuilder)))
+ || (path.ident.name == sym!(set_mode) && match_type(cx, obj_ty, &paths::PERMISSIONS));
+ if let ExprKind::Lit(_) = param.kind;
+
+ then {
+ let snip = match snippet_opt(cx, param.span) {
+ Some(s) => s,
+ _ => return,
+ };
+
+ if !snip.starts_with("0o") {
+ show_error(cx, param);
+ }
+ }
+ }
+ },
+ ExprKind::Call(func, [param]) => {
+ if_chain! {
+ if let ExprKind::Path(ref path) = func.kind;
+ if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE);
+ if let ExprKind::Lit(_) = param.kind;
+
+ then {
+ let snip = match snippet_opt(cx, param.span) {
+ Some(s) => s,
+ _ => return,
+ };
+
+ if !snip.starts_with("0o") {
+ show_error(cx, param);
+ }
+ }
+ }
+ },
+ _ => {},
+ };
+ }
+}
+
+fn show_error(cx: &LateContext<'_>, param: &Expr<'_>) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ NON_OCTAL_UNIX_PERMISSIONS,
+ param.span,
+ "using a non-octal value to set unix file permissions",
+ "consider using an octal literal instead",
+ format!(
+ "0o{}",
+ snippet_with_applicability(cx, param.span, "0o..", &mut applicability,),
+ ),
+ applicability,
+ );
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint_and_help;
+use std::{
+ fmt,
+ hash::{Hash, Hasher},
+};
+
- use rustc_span::{Span, Symbol};
++use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
++use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::hygiene::{ExpnKind, MacroKind};
- /// The (name, (open brace, close brace), source snippet)
- type MacroInfo<'a> = (Symbol, &'a (String, String), String);
++use rustc_span::Span;
+use serde::{de, Deserialize};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks that common macros are used with consistent bracing.
+ ///
+ /// ### Why is this bad?
+ /// This is mostly a consistency lint although using () or []
+ /// doesn't give you a semicolon in item position, which can be unexpected.
+ ///
+ /// ### Example
+ /// ```rust
+ /// vec!{1, 2, 3};
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// vec![1, 2, 3];
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub NONSTANDARD_MACRO_BRACES,
+ nursery,
+ "check consistent use of braces in macro"
+}
+
+const BRACES: &[(&str, &str)] = &[("(", ")"), ("{", "}"), ("[", "]")];
+
- if let Some((name, braces, snip)) = is_offending_macro(cx, item.span, self) {
- let span = item.span.ctxt().outer_expn_data().call_site;
- emit_help(cx, snip, braces, name, span);
++/// The (callsite span, (open brace, close brace), source snippet)
++type MacroInfo<'a> = (Span, &'a (String, String), String);
+
+#[derive(Clone, Debug, Default)]
+pub struct MacroBraces {
+ macro_braces: FxHashMap<String, (String, String)>,
+ done: FxHashSet<Span>,
+}
+
+impl MacroBraces {
+ pub fn new(conf: &FxHashSet<MacroMatcher>) -> Self {
+ let macro_braces = macro_braces(conf.clone());
+ Self {
+ macro_braces,
+ done: FxHashSet::default(),
+ }
+ }
+}
+
+impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]);
+
+impl EarlyLintPass for MacroBraces {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
- if let Some((name, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
- let span = stmt.span.ctxt().outer_expn_data().call_site;
- emit_help(cx, snip, braces, name, span);
++ if let Some((span, braces, snip)) = is_offending_macro(cx, item.span, self) {
++ emit_help(cx, &snip, braces, span);
+ self.done.insert(span);
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
- if let Some((name, braces, snip)) = is_offending_macro(cx, expr.span, self) {
- let span = expr.span.ctxt().outer_expn_data().call_site;
- emit_help(cx, snip, braces, name, span);
++ if let Some((span, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
++ emit_help(cx, &snip, braces, span);
+ self.done.insert(span);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
- if let Some((name, braces, snip)) = is_offending_macro(cx, ty.span, self) {
- let span = ty.span.ctxt().outer_expn_data().call_site;
- emit_help(cx, snip, braces, name, span);
++ if let Some((span, braces, snip)) = is_offending_macro(cx, expr.span, self) {
++ emit_help(cx, &snip, braces, span);
+ self.done.insert(span);
+ }
+ }
+
+ fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
- if let Some(snip) = snippet_opt(cx, span.ctxt().outer_expn_data().call_site);
++ if let Some((span, braces, snip)) = is_offending_macro(cx, ty.span, self) {
++ emit_help(cx, &snip, braces, span);
+ self.done.insert(span);
+ }
+ }
+}
+
+fn is_offending_macro<'a>(cx: &EarlyContext<'_>, span: Span, mac_braces: &'a MacroBraces) -> Option<MacroInfo<'a>> {
+ let unnested_or_local = || {
+ !span.ctxt().outer_expn_data().call_site.from_expansion()
+ || span
+ .macro_backtrace()
+ .last()
+ .map_or(false, |e| e.macro_def_id.map_or(false, DefId::is_local))
+ };
++ let span_call_site = span.ctxt().outer_expn_data().call_site;
+ if_chain! {
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind;
+ let name = mac_name.as_str();
+ if let Some(braces) = mac_braces.macro_braces.get(name);
- if snip.starts_with(&format!("{}!", name));
++ if let Some(snip) = snippet_opt(cx, span_call_site);
+ // we must check only invocation sites
+ // https://github.com/rust-lang/rust-clippy/issues/7422
- if !c.starts_with(&format!("{}!{}", name, braces.0));
- if !mac_braces.done.contains(&span.ctxt().outer_expn_data().call_site);
++ if snip.starts_with(&format!("{name}!"));
+ if unnested_or_local();
+ // make formatting consistent
+ let c = snip.replace(' ', "");
- Some((mac_name, braces, snip))
++ if !c.starts_with(&format!("{name}!{}", braces.0));
++ if !mac_braces.done.contains(&span_call_site);
+ then {
- fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: Symbol, span: Span) {
- let with_space = &format!("! {}", braces.0);
- let without_space = &format!("!{}", braces.0);
- let mut help = snip;
- for b in BRACES.iter().filter(|b| b.0 != braces.0) {
- help = help.replace(b.0, &braces.0).replace(b.1, &braces.1);
- // Only `{` traditionally has space before the brace
- if braces.0 != "{" && help.contains(with_space) {
- help = help.replace(with_space, without_space);
- } else if braces.0 == "{" && help.contains(without_space) {
- help = help.replace(without_space, with_space);
- }
++ Some((span_call_site, braces, snip))
+ } else {
+ None
+ }
+ }
+}
+
- span_lint_and_help(
- cx,
- NONSTANDARD_MACRO_BRACES,
- span,
- &format!("use of irregular braces for `{}!` macro", name),
- Some(span),
- &format!("consider writing `{}`", help),
- );
++fn emit_help(cx: &EarlyContext<'_>, snip: &str, braces: &(String, String), span: Span) {
++ if let Some((macro_name, macro_args_str)) = snip.split_once('!') {
++ let mut macro_args = macro_args_str.trim().to_string();
++ // now remove the wrong braces
++ macro_args.remove(0);
++ macro_args.pop();
++ span_lint_and_sugg(
++ cx,
++ NONSTANDARD_MACRO_BRACES,
++ span,
++ &format!("use of irregular braces for `{macro_name}!` macro"),
++ "consider writing",
++ format!("{macro_name}!{}{macro_args}{}", braces.0, braces.1),
++ Applicability::MachineApplicable,
++ );
+ }
- .ok_or_else(|| {
- de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{}`", brace))
- })?,
+}
+
+fn macro_braces(conf: FxHashSet<MacroMatcher>) -> FxHashMap<String, (String, String)> {
+ let mut braces = vec![
+ macro_matcher!(
+ name: "print",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "println",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "eprint",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "eprintln",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "write",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "writeln",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "format",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "format_args",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "vec",
+ braces: ("[", "]"),
+ ),
+ macro_matcher!(
+ name: "matches",
+ braces: ("(", ")"),
+ ),
+ ]
+ .into_iter()
+ .collect::<FxHashMap<_, _>>();
+ // We want users items to override any existing items
+ for it in conf {
+ braces.insert(it.name, it.braces);
+ }
+ braces
+}
+
+macro_rules! macro_matcher {
+ (name: $name:expr, braces: ($open:expr, $close:expr) $(,)?) => {
+ ($name.to_owned(), ($open.to_owned(), $close.to_owned()))
+ };
+}
+pub(crate) use macro_matcher;
+
+#[derive(Clone, Debug)]
+pub struct MacroMatcher {
+ name: String,
+ braces: (String, String),
+}
+
+impl Hash for MacroMatcher {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.name.hash(state);
+ }
+}
+
+impl PartialEq for MacroMatcher {
+ fn eq(&self, other: &Self) -> bool {
+ self.name == other.name
+ }
+}
+impl Eq for MacroMatcher {}
+
+impl<'de> Deserialize<'de> for MacroMatcher {
+ fn deserialize<D>(deser: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(field_identifier, rename_all = "lowercase")]
+ enum Field {
+ Name,
+ Brace,
+ }
+ struct MacVisitor;
+ impl<'de> de::Visitor<'de> for MacVisitor {
+ type Value = MacroMatcher;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("struct MacroMatcher")
+ }
+
+ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
+ where
+ V: de::MapAccess<'de>,
+ {
+ let mut name = None;
+ let mut brace: Option<&str> = None;
+ while let Some(key) = map.next_key()? {
+ match key {
+ Field::Name => {
+ if name.is_some() {
+ return Err(de::Error::duplicate_field("name"));
+ }
+ name = Some(map.next_value()?);
+ },
+ Field::Brace => {
+ if brace.is_some() {
+ return Err(de::Error::duplicate_field("brace"));
+ }
+ brace = Some(map.next_value()?);
+ },
+ }
+ }
+ let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
+ let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
+ Ok(MacroMatcher {
+ name,
+ braces: BRACES
+ .iter()
+ .find(|b| b.0 == brace)
+ .map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
++ .ok_or_else(|| de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
+ })
+ }
+ }
+
+ const FIELDS: &[&str] = &["name", "brace"];
+ deser.deserialize_struct("MacroMatcher", FIELDS, MacVisitor)
+ }
+}
--- /dev/null
- write!(suggest_1, "\\x{:02x}", n).unwrap();
+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(lit) = &expr.kind {
+ if matches!(lit.token_lit.kind, LitKind::Str) {
+ check_lit(cx, &lit.token_lit, lit.span, true);
+ } else if matches!(lit.token_lit.kind, LitKind::ByteStr) {
+ check_lit(cx, &lit.token_lit, lit.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
- "because `{}` is the {} value for this type, {}",
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+
+use clippy_utils::comparisons::{normalize_comparison, Rel};
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_isize_or_usize;
+use clippy_utils::{clip, int_bits, unsext};
+
+use super::ABSURD_EXTREME_COMPARISONS;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ op: BinOpKind,
+ lhs: &'tcx Expr<'_>,
+ rhs: &'tcx Expr<'_>,
+) {
+ if let Some((culprit, result)) = detect_absurd_comparison(cx, op, lhs, rhs) {
+ let msg = "this comparison involving the minimum or maximum element for this \
+ type contains a case that is always true or always false";
+
+ let conclusion = match result {
+ AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
+ AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
+ AbsurdComparisonResult::InequalityImpossible => format!(
+ "the case where the two sides are not equal never occurs, consider using `{} == {}` \
+ instead",
+ snippet(cx, lhs.span, "lhs"),
+ snippet(cx, rhs.span, "rhs")
+ ),
+ };
+
+ let help = format!(
- },
- conclusion
++ "because `{}` is the {} value for this type, {conclusion}",
+ snippet(cx, culprit.expr.span, "x"),
+ match culprit.which {
+ ExtremeType::Minimum => "minimum",
+ ExtremeType::Maximum => "maximum",
++ }
+ );
+
+ span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
+ }
+}
+
+enum ExtremeType {
+ Minimum,
+ Maximum,
+}
+
+struct ExtremeExpr<'a> {
+ which: ExtremeType,
+ expr: &'a Expr<'a>,
+}
+
+enum AbsurdComparisonResult {
+ AlwaysFalse,
+ AlwaysTrue,
+ InequalityImpossible,
+}
+
+fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ if let ExprKind::Cast(cast_exp, _) = expr.kind {
+ let precast_ty = cx.typeck_results().expr_ty(cast_exp);
+ let cast_ty = cx.typeck_results().expr_ty(expr);
+
+ return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty);
+ }
+
+ false
+}
+
+fn detect_absurd_comparison<'tcx>(
+ cx: &LateContext<'tcx>,
+ op: BinOpKind,
+ lhs: &'tcx Expr<'_>,
+ rhs: &'tcx Expr<'_>,
+) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> {
+ use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible};
+ use ExtremeType::{Maximum, Minimum};
+ // absurd comparison only makes sense on primitive types
+ // primitive types don't implement comparison operators with each other
+ if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) {
+ return None;
+ }
+
+ // comparisons between fix sized types and target sized types are considered unanalyzable
+ if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) {
+ return None;
+ }
+
+ let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;
+
+ let lx = detect_extreme_expr(cx, normalized_lhs);
+ let rx = detect_extreme_expr(cx, normalized_rhs);
+
+ Some(match rel {
+ Rel::Lt => {
+ match (lx, rx) {
+ (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x
+ (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min
+ _ => return None,
+ }
+ },
+ Rel::Le => {
+ match (lx, rx) {
+ (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x
+ (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x
+ (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min
+ (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max
+ _ => return None,
+ }
+ },
+ Rel::Ne | Rel::Eq => return None,
+ })
+}
+
+fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ let cv = constant(cx, cx.typeck_results(), expr)?.0;
+
+ let which = match (ty.kind(), cv) {
+ (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,
+ (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => {
+ ExtremeType::Minimum
+ },
+
+ (&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum,
+ (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => {
+ ExtremeType::Maximum
+ },
+ (&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum,
+
+ _ => return None,
+ };
+ Some(ExtremeExpr { which, expr })
+}
--- /dev/null
- #![allow(
- // False positive
- clippy::match_same_arms
- )]
-
+use super::ARITHMETIC_SIDE_EFFECTS;
+use clippy_utils::{consts::constant_simple, diagnostics::span_lint};
+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::string::String",
+ "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,
+ }
+ }
+
+ /// 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::BinOpKind::Add | hir::BinOpKind::Sub = op.node
- && let hir::ExprKind::Lit(ref lit) = expr.kind
- && let ast::LitKind::Int(0, _) = lit.node
- {
- return true;
- }
- if let hir::BinOpKind::Div | hir::BinOpKind::Rem = op.node
- && let hir::ExprKind::Lit(ref lit) = expr.kind
- && !matches!(lit.node, ast::LitKind::Int(0, _))
++ if let hir::ExprKind::Lit(ref lit) = expr.kind &&
++ let ast::LitKind::Int(value, _) = lit.node
+ {
- return true;
- }
- if let hir::BinOpKind::Mul = op.node
- && let hir::ExprKind::Lit(ref lit) = expr.kind
- && let ast::LitKind::Int(0 | 1, _) = lit.node
- {
- return true;
++ 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
+ }
- false
+ }
+
+ /// Checks if the given `expr` has any of the inner `allowed` elements.
- fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
- self.allowed.contains(
- cx.typeck_results()
- .expr_ty(expr)
- .to_string()
- .split('<')
- .next()
- .unwrap_or_default(),
- )
++ fn is_allowed_ty(&self, ty: Ty<'_>) -> bool {
++ self.allowed
++ .contains(ty.to_string().split('<').next().unwrap_or_default())
+ }
+
- /// Explicit integers like `1` or `i32::MAX`. Does not take into consideration references.
- fn is_literal_integer(expr: &hir::Expr<'_>, expr_refs: Ty<'_>) -> bool {
- let is_integral = expr_refs.is_integral();
- let is_literal = matches!(expr.kind, hir::ExprKind::Lit(_));
- is_integral && is_literal
++ // 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 `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));
++ }
++ if let hir::ExprKind::AddrOf(.., inn) = expr.kind && let hir::ExprKind::Lit(_) = inn.kind {
++ return Some(LiteralIntegerTy::Ref(inn));
++ }
++ None
++ }
++
+ /// 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(
++ fn manage_bin_ops<'tcx>(
+ &mut self,
- cx: &LateContext<'_>,
- expr: &hir::Expr<'_>,
++ cx: &LateContext<'tcx>,
++ expr: &hir::Expr<'tcx>,
+ op: &Spanned<hir::BinOpKind>,
- lhs: &hir::Expr<'_>,
- rhs: &hir::Expr<'_>,
++ 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;
+ };
- if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) {
++ 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 = match (
- Self::is_literal_integer(lhs, cx.typeck_results().expr_ty(lhs).peel_refs()),
- Self::is_literal_integer(rhs, cx.typeck_results().expr_ty(rhs).peel_refs()),
- ) {
- (true, true) => true,
- (true, false) => Self::has_valid_op(op, lhs),
- (false, true) => Self::has_valid_op(op, rhs),
- (false, false) => false,
++ let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
++ 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,
++ }
++ } else {
++ false
+ };
+ if !has_valid_op {
+ self.issue_lint(cx, expr);
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
- fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
+ if self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) {
+ return;
+ }
+ match &expr.kind {
+ hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => {
+ self.manage_bin_ops(cx, expr, op, lhs, rhs);
+ },
+ hir::ExprKind::Unary(hir::UnOp::Neg, _) => {
+ if constant_simple(cx, cx.typeck_results(), expr).is_none() {
+ self.issue_lint(cx, expr);
+ }
+ },
+ _ => {},
+ }
+ }
+
+ 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;
+ }
+ }
+}
++
++/// 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,
++ }
++ }
++}
--- /dev/null
- use rustc_hir::intravisit::{walk_expr, Visitor};
+use clippy_utils::binop_traits;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
++use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{eq_expr_value, trait_ref_of_method};
++use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
- use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
++use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use rustc_lint::LateContext;
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::BorrowKind;
+use rustc_trait_selection::infer::TyCtxtInferExt;
- format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
+
+use super::ASSIGN_OP_PATTERN;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ assignee: &'tcx hir::Expr<'_>,
+ e: &'tcx hir::Expr<'_>,
+) {
+ if let hir::ExprKind::Binary(op, l, r) = &e.kind {
+ let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
+ let ty = cx.typeck_results().expr_ty(assignee);
+ let rty = cx.typeck_results().expr_ty(rhs);
+ if_chain! {
+ if let Some((_, lang_item)) = binop_traits(op.node);
+ if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
+ let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id).def_id;
+ if trait_ref_of_method(cx, parent_fn)
+ .map_or(true, |t| t.path.res.def_id() != trait_id);
+ if implements_trait(cx, ty, trait_id, &[rty.into()]);
+ then {
+ // Primitive types execute assign-ops right-to-left. Every other type is left-to-right.
+ if !(ty.is_primitive() && rty.is_primitive()) {
+ // TODO: This will have false negatives as it doesn't check if the borrows are
+ // actually live at the end of their respective expressions.
+ let mut_borrows = mut_borrows_in_expr(cx, assignee);
+ let imm_borrows = imm_borrows_in_expr(cx, rhs);
+ if mut_borrows.iter().any(|id| imm_borrows.contains(id)) {
+ return;
+ }
+ }
+ span_lint_and_then(
+ cx,
+ ASSIGN_OP_PATTERN,
+ expr.span,
+ "manual implementation of an assign operation",
+ |diag| {
+ if let (Some(snip_a), Some(snip_r)) =
+ (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
+ {
+ diag.span_suggestion(
+ expr.span,
+ "replace it with",
- let mut visitor = ExprVisitor {
- assignee,
- counter: 0,
- cx,
- };
-
- walk_expr(&mut visitor, e);
++ format!("{snip_a} {}= {snip_r}", op.node.as_str()),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+ }
+ };
+
- if visitor.counter == 1 {
++ let mut found = false;
++ let found_multiple = for_each_expr(e, |e| {
++ if eq_expr_value(cx, assignee, e) {
++ if found {
++ return ControlFlow::Break(());
++ }
++ found = true;
++ }
++ ControlFlow::Continue(())
++ })
++ .is_some();
+
- struct ExprVisitor<'a, 'tcx> {
- assignee: &'a hir::Expr<'a>,
- counter: u8,
- cx: &'a LateContext<'tcx>,
- }
-
- impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- if eq_expr_value(self.cx, self.assignee, expr) {
- self.counter += 1;
- }
-
- walk_expr(self, expr);
- }
- }
-
++ if found && !found_multiple {
+ // a = a op b
+ if eq_expr_value(cx, assignee, l) {
+ lint(assignee, r);
+ }
+ // a = b commutative_op a
+ // Limited to primitive type as these ops are know to be commutative
+ if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
+ match op.node {
+ hir::BinOpKind::Add
+ | hir::BinOpKind::Mul
+ | hir::BinOpKind::And
+ | hir::BinOpKind::Or
+ | hir::BinOpKind::BitXor
+ | hir::BinOpKind::BitAnd
+ | hir::BinOpKind::BitOr => {
+ lint(assignee, l);
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn imm_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
+ struct S(hir::HirIdSet);
+ impl Delegate<'_> for S {
+ fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) {
+ if matches!(kind, BorrowKind::ImmBorrow | BorrowKind::UniqueImmBorrow) {
+ self.0.insert(match place.place.base {
+ PlaceBase::Local(id) => id,
+ PlaceBase::Upvar(id) => id.var_path.hir_id,
+ _ => return,
+ });
+ }
+ }
+
+ fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
+ fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
+ fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {}
+ fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
+ }
+
+ let mut s = S(hir::HirIdSet::default());
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ let mut v = ExprUseVisitor::new(
+ &mut s,
+ &infcx,
+ cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
+ cx.param_env,
+ cx.typeck_results(),
+ );
+ v.consume_expr(e);
+ });
+ s.0
+}
+
+fn mut_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
+ struct S(hir::HirIdSet);
+ impl Delegate<'_> for S {
+ fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) {
+ if matches!(kind, BorrowKind::MutBorrow) {
+ self.0.insert(match place.place.base {
+ PlaceBase::Local(id) => id,
+ PlaceBase::Upvar(id) => id.var_path.hir_id,
+ _ => return,
+ });
+ }
+ }
+
+ fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
+ fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
+ fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {}
+ fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
+ }
+
+ let mut s = S(hir::HirIdSet::default());
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ let mut v = ExprUseVisitor::new(
+ &mut s,
+ &infcx,
+ cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
+ cx.param_env,
+ cx.typeck_results(),
+ );
+ v.consume_expr(e);
+ });
+ s.0
+}
--- /dev/null
- &format!(
- "incompatible bit mask: `_ & {}` can never be equal to `{}`",
- mask_value, cmp_value
- ),
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+
+use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK};
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ op: BinOpKind,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+) {
+ if op.is_comparison() {
+ if let Some(cmp_opt) = fetch_int_literal(cx, right) {
+ check_compare(cx, left, op, cmp_opt, e.span);
+ } else if let Some(cmp_val) = fetch_int_literal(cx, left) {
+ check_compare(cx, right, invert_cmp(op), cmp_val, e.span);
+ }
+ }
+}
+
+#[must_use]
+fn invert_cmp(cmp: BinOpKind) -> BinOpKind {
+ match cmp {
+ BinOpKind::Eq => BinOpKind::Eq,
+ BinOpKind::Ne => BinOpKind::Ne,
+ BinOpKind::Lt => BinOpKind::Gt,
+ BinOpKind::Gt => BinOpKind::Lt,
+ BinOpKind::Le => BinOpKind::Ge,
+ BinOpKind::Ge => BinOpKind::Le,
+ _ => BinOpKind::Or, // Dummy
+ }
+}
+
+fn check_compare(cx: &LateContext<'_>, bit_op: &Expr<'_>, cmp_op: BinOpKind, cmp_value: u128, span: Span) {
+ if let ExprKind::Binary(op, left, right) = &bit_op.kind {
+ if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr {
+ return;
+ }
+ fetch_int_literal(cx, right)
+ .or_else(|| fetch_int_literal(cx, left))
+ .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span));
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_bit_mask(
+ cx: &LateContext<'_>,
+ bit_op: BinOpKind,
+ cmp_op: BinOpKind,
+ mask_value: u128,
+ cmp_value: u128,
+ span: Span,
+) {
+ match cmp_op {
+ BinOpKind::Eq | BinOpKind::Ne => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value & cmp_value != cmp_value {
+ if cmp_value != 0 {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
- &format!(
- "incompatible bit mask: `_ | {}` can never be equal to `{}`",
- mask_value, cmp_value
- ),
++ &format!("incompatible bit mask: `_ & {mask_value}` can never be equal to `{cmp_value}`"),
+ );
+ }
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value | cmp_value != cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
- &format!(
- "incompatible bit mask: `_ & {}` will always be lower than `{}`",
- mask_value, cmp_value
- ),
++ &format!("incompatible bit mask: `_ | {mask_value}` can never be equal to `{cmp_value}`"),
+ );
+ }
+ },
+ _ => (),
+ },
+ BinOpKind::Lt | BinOpKind::Ge => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value < cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
- &format!(
- "incompatible bit mask: `_ | {}` will never be lower than `{}`",
- mask_value, cmp_value
- ),
++ &format!("incompatible bit mask: `_ & {mask_value}` will always be lower than `{cmp_value}`"),
+ );
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value >= cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
- &format!(
- "incompatible bit mask: `_ & {}` will never be higher than `{}`",
- mask_value, cmp_value
- ),
++ &format!("incompatible bit mask: `_ | {mask_value}` will never be lower than `{cmp_value}`"),
+ );
+ } else {
+ check_ineffective_lt(cx, span, mask_value, cmp_value, "|");
+ }
+ },
+ BinOpKind::BitXor => check_ineffective_lt(cx, span, mask_value, cmp_value, "^"),
+ _ => (),
+ },
+ BinOpKind::Le | BinOpKind::Gt => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value <= cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
- &format!(
- "incompatible bit mask: `_ | {}` will always be higher than `{}`",
- mask_value, cmp_value
- ),
++ &format!("incompatible bit mask: `_ & {mask_value}` will never be higher than `{cmp_value}`"),
+ );
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value > cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
- &format!(
- "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
- op, m, c
- ),
++ &format!("incompatible bit mask: `_ | {mask_value}` will always be higher than `{cmp_value}`"),
+ );
+ } else {
+ check_ineffective_gt(cx, span, mask_value, cmp_value, "|");
+ }
+ },
+ BinOpKind::BitXor => check_ineffective_gt(cx, span, mask_value, cmp_value, "^"),
+ _ => (),
+ },
+ _ => (),
+ }
+}
+
+fn check_ineffective_lt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) {
+ if c.is_power_of_two() && m < c {
+ span_lint(
+ cx,
+ INEFFECTIVE_BIT_MASK,
+ span,
- &format!(
- "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
- op, m, c
- ),
++ &format!("ineffective bit mask: `x {op} {m}` compared to `{c}`, is the same as x compared directly"),
+ );
+ }
+}
+
+fn check_ineffective_gt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) {
+ if (c + 1).is_power_of_two() && m <= c {
+ span_lint(
+ cx,
+ INEFFECTIVE_BIT_MASK,
+ span,
++ &format!("ineffective bit mask: `x {op} {m}` compared to `{c}`, is the same as x compared directly"),
+ );
+ }
+}
+
+fn fetch_int_literal(cx: &LateContext<'_>, lit: &Expr<'_>) -> Option<u128> {
+ match constant(cx, cx.typeck_results(), lit)?.0 {
+ Constant::Int(n) => Some(n),
+ _ => None,
+ }
+}
--- /dev/null
- expr_snip = format!("*{}", arg_snip);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{implements_trait, is_copy};
+use clippy_utils::{match_any_def_paths, path_def_id, paths};
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::symbol::sym;
+
+use super::CMP_OWNED;
+
+pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) {
+ if op.is_comparison() {
+ check_op(cx, lhs, rhs, true);
+ check_op(cx, rhs, lhs, false);
+ }
+}
+
+#[derive(Default)]
+struct EqImpl {
+ ty_eq_other: bool,
+ other_eq_ty: bool,
+}
+impl EqImpl {
+ fn is_implemented(&self) -> bool {
+ self.ty_eq_other || self.other_eq_ty
+ }
+}
+
+fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> {
+ cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl {
+ ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]),
+ other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]),
+ })
+}
+
+fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
+ let typeck = cx.typeck_results();
+ let (arg, arg_span) = match expr.kind {
+ ExprKind::MethodCall(_, arg, [], _)
+ if typeck
+ .type_dependent_def_id(expr.hir_id)
+ .and_then(|id| cx.tcx.trait_of_item(id))
+ .map_or(false, |id| {
+ matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
+ }) =>
+ {
+ (arg, arg.span)
+ },
+ ExprKind::Call(path, [arg])
+ if path_def_id(cx, path)
+ .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
+ .map_or(false, |idx| match idx {
+ 0 => true,
+ 1 => !is_copy(cx, typeck.expr_ty(expr)),
+ _ => false,
+ }) =>
+ {
+ (arg, arg.span)
+ },
+ _ => return,
+ };
+
+ let arg_ty = typeck.expr_ty(arg);
+ let other_ty = typeck.expr_ty(other);
+
+ let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
+ let with_deref = arg_ty
+ .builtin_deref(true)
+ .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
+ .unwrap_or_default();
+
+ if !with_deref.is_implemented() && !without_deref.is_implemented() {
+ return;
+ }
+
+ let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
+
+ let lint_span = if other_gets_derefed {
+ expr.span.to(other.span)
+ } else {
+ expr.span
+ };
+
+ span_lint_and_then(
+ cx,
+ CMP_OWNED,
+ lint_span,
+ "this creates an owned instance just for comparison",
+ |diag| {
+ // This also catches `PartialEq` implementations that call `to_owned`.
+ if other_gets_derefed {
+ diag.span_label(lint_span, "try implementing the comparison without allocating");
+ return;
+ }
+
+ let arg_snip = snippet(cx, arg_span, "..");
+ let expr_snip;
+ let eq_impl;
+ if with_deref.is_implemented() {
- "{}{}{}",
- expr_snip,
++ expr_snip = format!("*{arg_snip}");
+ eq_impl = with_deref;
+ } else {
+ expr_snip = arg_snip.to_string();
+ eq_impl = without_deref;
+ };
+
+ let span;
+ let hint;
+ if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
+ span = expr.span;
+ hint = expr_snip;
+ } else {
+ span = expr.span.to(other.span);
+
+ let cmp_span = if other.span < expr.span {
+ other.span.between(expr.span)
+ } else {
+ expr.span.between(other.span)
+ };
+ if eq_impl.ty_eq_other {
+ hint = format!(
- "{}{}{}",
++ "{expr_snip}{}{}",
+ snippet(cx, cmp_span, ".."),
+ snippet(cx, other.span, "..")
+ );
+ } else {
+ hint = format!(
- snippet(cx, cmp_span, ".."),
- expr_snip
++ "{}{}{expr_snip}",
+ snippet(cx, other.span, ".."),
++ snippet(cx, cmp_span, "..")
+ );
+ }
+ }
+
+ diag.span_suggestion(
+ span,
+ "try",
+ hint,
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+}
--- /dev/null
- &format!("calling `{}()` is more concise than this calculation", suggested_fn),
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::DURATION_SUBSEC;
+
+pub(crate) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ op: BinOpKind,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+) {
+ if op == BinOpKind::Div
+ && let ExprKind::MethodCall(method_path, self_arg, [], _) = left.kind
+ && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration)
+ && let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right)
+ {
+ let suggested_fn = match (method_path.ident.as_str(), divisor) {
+ ("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis",
+ ("subsec_nanos", 1_000) => "subsec_micros",
+ _ => return,
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ DURATION_SUBSEC,
+ expr.span,
- "{}.{}()",
- snippet_with_applicability(cx, self_arg.span, "_", &mut applicability),
- suggested_fn
++ &format!("calling `{suggested_fn}()` is more concise than this calculation"),
+ "try",
+ format!(
++ "{}.{suggested_fn}()",
++ snippet_with_applicability(cx, self_arg.span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
- &format!("identical args used in this `{}!` macro call", macro_name),
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
+use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function};
+use rustc_hir::{BinOpKind, Expr};
+use rustc_lint::LateContext;
+
+use super::EQ_OP;
+
+pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let Some((macro_call, macro_name))
+ = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
+ let name = cx.tcx.item_name(macro_call.def_id);
+ matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne")
+ .then(|| (macro_call, name))
+ })
+ && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
+ && eq_expr_value(cx, lhs, rhs)
+ && macro_call.is_local()
+ && !is_in_test_function(cx.tcx, e.hir_id)
+ {
+ span_lint(
+ cx,
+ EQ_OP,
+ lhs.span.to(rhs.span),
++ &format!("identical args used in this `{macro_name}!` macro call"),
+ );
+ }
+}
+
+pub(crate) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ op: BinOpKind,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+) {
+ if is_useless_with_eq_exprs(op.into()) && eq_expr_value(cx, left, right) && !is_in_test_function(cx.tcx, e.hir_id) {
+ span_lint(
+ cx,
+ EQ_OP,
+ e.span,
+ &format!("equal expressions as operands to `{}`", op.as_str()),
+ );
+ }
+}
--- /dev/null
- let long = format!("{} = {}", snip_a, sugg::make_binop(op.into(), a, r));
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::eq_expr_value;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::sugg;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+
+use super::MISREFACTORED_ASSIGN_OP;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ op: hir::BinOpKind,
+ lhs: &'tcx hir::Expr<'_>,
+ rhs: &'tcx hir::Expr<'_>,
+) {
+ if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind {
+ if op != binop.node {
+ return;
+ }
+ // lhs op= l op r
+ if eq_expr_value(cx, lhs, l) {
+ lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, r);
+ }
+ // lhs op= l commutative_op r
+ if is_commutative(op) && eq_expr_value(cx, lhs, r) {
+ lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, l);
+ }
+ }
+}
+
+fn lint_misrefactored_assign_op(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ op: hir::BinOpKind,
+ rhs: &hir::Expr<'_>,
+ assignee: &hir::Expr<'_>,
+ rhs_other: &hir::Expr<'_>,
+) {
+ span_lint_and_then(
+ cx,
+ MISREFACTORED_ASSIGN_OP,
+ expr.span,
+ "variable appears on both sides of an assignment operation",
+ |diag| {
+ if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) {
+ let a = &sugg::Sugg::hir(cx, assignee, "..");
+ let r = &sugg::Sugg::hir(cx, rhs, "..");
- "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
- snip_a,
- snip_a,
- op.as_str(),
- snip_r,
- long
++ let long = format!("{snip_a} = {}", sugg::make_binop(op.into(), a, r));
+ diag.span_suggestion(
+ expr.span,
+ &format!(
- format!("{} {}= {}", snip_a, op.as_str(), snip_r),
++ "did you mean `{snip_a} = {snip_a} {} {snip_r}` or `{long}`? Consider replacing it with",
++ op.as_str()
+ ),
++ format!("{snip_a} {}= {snip_r}", op.as_str()),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or",
+ long,
+ Applicability::MaybeIncorrect, // snippet
+ );
+ }
+ },
+ );
+}
+
+#[must_use]
+fn is_commutative(op: hir::BinOpKind) -> bool {
+ use rustc_hir::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ match op {
+ Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true,
+ Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false,
+ }
+}
--- /dev/null
- /// Known safe built-in types like `Wrapping` or `Saturing`, floats, operations in constant
+mod absurd_extreme_comparisons;
+mod assign_op_pattern;
+mod bit_mask;
+mod cmp_nan;
+mod cmp_owned;
+mod double_comparison;
+mod duration_subsec;
+mod eq_op;
+mod erasing_op;
+mod float_cmp;
+mod float_equality_without_abs;
+mod identity_op;
+mod integer_division;
+mod misrefactored_assign_op;
+mod modulo_arithmetic;
+mod modulo_one;
+mod needless_bitwise_bool;
+mod numeric_arithmetic;
+mod op_ref;
+mod ptr_eq;
+mod self_assignment;
+mod verbose_bit_mask;
+
+pub(crate) mod arithmetic_side_effects;
+
+use rustc_hir::{Body, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for comparisons where one side of the relation is
+ /// either the minimum or maximum value for its type and warns if it involves a
+ /// case that is always true or always false. Only integer and boolean types are
+ /// checked.
+ ///
+ /// ### Why is this bad?
+ /// An expression like `min <= x` may misleadingly imply
+ /// that it is possible for `x` to be less than the minimum. Expressions like
+ /// `max < x` are probably mistakes.
+ ///
+ /// ### Known problems
+ /// For `usize` the size of the current compile target will
+ /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
+ /// a comparison to detect target pointer width will trigger this lint. One can
+ /// use `mem::sizeof` and compare its value or conditional compilation
+ /// attributes
+ /// like `#[cfg(target_pointer_width = "64")] ..` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec: Vec<isize> = Vec::new();
+ /// if vec.len() <= 0 {}
+ /// if 100 > i32::MAX {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ABSURD_EXTREME_COMPARISONS,
+ correctness,
+ "a comparison with a maximum or minimum value that is always true or false"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks any kind of arithmetic operation of any type.
+ ///
+ /// Operators like `+`, `-`, `*` or `<<` are usually capable of overflowing according to the [Rust
+ /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
+ /// or can panic (`/`, `%`).
+ ///
++ /// Known safe built-in types like `Wrapping` or `Saturating`, floats, operations in constant
+ /// environments, allowed types and non-constant operations that won't overflow are ignored.
+ ///
+ /// ### Why is this bad?
+ /// For integers, overflow will trigger a panic in debug builds or wrap the result in
+ /// release mode; division by zero will cause a panic in either mode. As a result, it is
+ /// desirable to explicitly call checked, wrapping or saturating arithmetic methods.
+ ///
+ /// #### Example
+ /// ```rust
+ /// // `n` can be any number, including `i32::MAX`.
+ /// fn foo(n: i32) -> i32 {
+ /// n + 1
+ /// }
+ /// ```
+ ///
+ /// Third-party types can also overflow or present unwanted side-effects.
+ ///
+ /// #### Example
+ /// ```ignore,rust
+ /// use rust_decimal::Decimal;
+ /// let _n = Decimal::MAX + Decimal::MAX;
+ /// ```
+ ///
+ /// ### Allowed types
+ /// Custom allowed types can be specified through the "arithmetic-side-effects-allowed" filter.
+ #[clippy::version = "1.64.0"]
+ pub ARITHMETIC_SIDE_EFFECTS,
+ restriction,
+ "any arithmetic expression that can cause side effects like overflows or panics"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for integer arithmetic operations which could overflow or panic.
+ ///
+ /// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable
+ /// of overflowing according to the [Rust
+ /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
+ /// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is
+ /// attempted.
+ ///
+ /// ### Why is this bad?
+ /// Integer overflow will trigger a panic in debug builds or will wrap in
+ /// release mode. Division by zero will cause a panic in either mode. In some applications one
+ /// wants explicitly checked, wrapping or saturating arithmetic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = 0;
+ /// a + 1;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INTEGER_ARITHMETIC,
+ restriction,
+ "any integer arithmetic expression which could overflow or panic"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for float arithmetic.
+ ///
+ /// ### Why is this bad?
+ /// For some embedded systems or kernel development, it
+ /// can be useful to rule out floating-point numbers.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = 0.0;
+ /// a + 1.0;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_ARITHMETIC,
+ restriction,
+ "any floating-point arithmetic statement"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `a = a op b` or `a = b commutative_op a`
+ /// patterns.
+ ///
+ /// ### Why is this bad?
+ /// These can be written as the shorter `a op= b`.
+ ///
+ /// ### Known problems
+ /// While forbidden by the spec, `OpAssign` traits may have
+ /// implementations that differ from the regular `Op` impl.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 0;
+ /// // ...
+ ///
+ /// a = a + b;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 0;
+ /// // ...
+ ///
+ /// a += b;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ASSIGN_OP_PATTERN,
+ style,
+ "assigning the result of an operation on a variable to that same variable"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `a op= a op b` or `a op= b op a` patterns.
+ ///
+ /// ### Why is this bad?
+ /// Most likely these are bugs where one meant to write `a
+ /// op= b`.
+ ///
+ /// ### Known problems
+ /// Clippy cannot know for sure if `a op= a op b` should have
+ /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
+ /// If `a op= a op b` is really the correct behavior it should be
+ /// written as `a = a op a op b` as it's less confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 2;
+ /// // ...
+ /// a += a + b;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MISREFACTORED_ASSIGN_OP,
+ suspicious,
+ "having a variable on both sides of an assign op"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for incompatible bit masks in comparisons.
+ ///
+ /// The formula for detecting if an expression of the type `_ <bit_op> m
+ /// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
+ /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
+ /// table:
+ ///
+ /// |Comparison |Bit Op|Example |is always|Formula |
+ /// |------------|------|-------------|---------|----------------------|
+ /// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` |
+ /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
+ /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
+ /// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` |
+ /// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` |
+ /// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` |
+ ///
+ /// ### Why is this bad?
+ /// If the bits that the comparison cares about are always
+ /// set to zero or one by the bit mask, the comparison is constant `true` or
+ /// `false` (depending on mask, compared value, and operators).
+ ///
+ /// So the code is actively misleading, and the only reason someone would write
+ /// this intentionally is to win an underhanded Rust contest or create a
+ /// test-case for this lint.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if (x & 1 == 2) { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub BAD_BIT_MASK,
+ correctness,
+ "expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bit masks in comparisons which can be removed
+ /// without changing the outcome. The basic structure can be seen in the
+ /// following table:
+ ///
+ /// |Comparison| Bit Op |Example |equals |
+ /// |----------|----------|------------|-------|
+ /// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`|
+ /// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`|
+ ///
+ /// ### Why is this bad?
+ /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
+ /// but still a bit misleading, because the bit mask is ineffective.
+ ///
+ /// ### Known problems
+ /// False negatives: This lint will only match instances
+ /// where we have figured out the math (which is for a power-of-two compared
+ /// value). This means things like `x | 1 >= 7` (which would be better written
+ /// as `x >= 6`) will not be reported (but bit masks like this are fairly
+ /// uncommon).
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if (x | 1 > 3) { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INEFFECTIVE_BIT_MASK,
+ correctness,
+ "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bit masks that can be replaced by a call
+ /// to `trailing_zeros`
+ ///
+ /// ### Why is this bad?
+ /// `x.trailing_zeros() > 4` is much clearer than `x & 15
+ /// == 0`
+ ///
+ /// ### Known problems
+ /// llvm generates better code for `x & 15 == 0` on x86
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if x & 0b1111 == 0 { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub VERBOSE_BIT_MASK,
+ pedantic,
+ "expressions where a bit mask is less readable than the corresponding method call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for double comparisons that could be simplified to a single expression.
+ ///
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 2;
+ /// if x == y || x < y {}
+ /// ```
+ ///
+ /// Use instead:
+ ///
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 2;
+ /// if x <= y {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DOUBLE_COMPARISONS,
+ complexity,
+ "unnecessary double comparisons that can be simplified"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calculation of subsecond microseconds or milliseconds
+ /// from other `Duration` methods.
+ ///
+ /// ### Why is this bad?
+ /// It's more concise to call `Duration::subsec_micros()` or
+ /// `Duration::subsec_millis()` than to calculate them.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::time::Duration;
+ /// # let duration = Duration::new(5, 0);
+ /// let micros = duration.subsec_nanos() / 1_000;
+ /// let millis = duration.subsec_nanos() / 1_000_000;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # use std::time::Duration;
+ /// # let duration = Duration::new(5, 0);
+ /// let micros = duration.subsec_micros();
+ /// let millis = duration.subsec_millis();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub DURATION_SUBSEC,
+ complexity,
+ "checks for calculation of subsecond microseconds or milliseconds"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for equal operands to comparison, logical and
+ /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
+ /// `||`, `&`, `|`, `^`, `-` and `/`).
+ ///
+ /// ### Why is this bad?
+ /// This is usually just a typo or a copy and paste error.
+ ///
+ /// ### Known problems
+ /// False negatives: We had some false positives regarding
+ /// calls (notably [racer](https://github.com/phildawes/racer) had one instance
+ /// of `x.pop() && x.pop()`), so we removed matching any function or method
+ /// calls. We may introduce a list of known pure functions in the future.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if x + 1 == x + 1 {}
+ ///
+ /// // or
+ ///
+ /// # let a = 3;
+ /// # let b = 4;
+ /// assert_eq!(a, a);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub EQ_OP,
+ correctness,
+ "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for arguments to `==` which have their address
+ /// taken to satisfy a bound
+ /// and suggests to dereference the other argument instead
+ ///
+ /// ### Why is this bad?
+ /// It is more idiomatic to dereference the other argument.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// &x == y
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// x == *y
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub OP_REF,
+ style,
+ "taking a reference to satisfy the type constraints on `==`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for erasing operations, e.g., `x * 0`.
+ ///
+ /// ### Why is this bad?
+ /// The whole expression can be replaced by zero.
+ /// This is most likely not the intended outcome and should probably be
+ /// corrected
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1;
+ /// 0 / x;
+ /// 0 * x;
+ /// x & 0;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ERASING_OP,
+ correctness,
+ "using erasing operations, e.g., `x * 0` or `y & 0`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for statements of the form `(a - b) < f32::EPSILON` or
+ /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
+ ///
+ /// ### Why is this bad?
+ /// The code without `.abs()` is more likely to have a bug.
+ ///
+ /// ### Known problems
+ /// If the user can ensure that b is larger than a, the `.abs()` is
+ /// technically unnecessary. However, it will make the code more robust and doesn't have any
+ /// large performance implications. If the abs call was deliberately left out for performance
+ /// reasons, it is probably better to state this explicitly in the code, which then can be done
+ /// with an allow.
+ ///
+ /// ### Example
+ /// ```rust
+ /// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ /// (a - b) < f32::EPSILON
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ /// (a - b).abs() < f32::EPSILON
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub FLOAT_EQUALITY_WITHOUT_ABS,
+ suspicious,
+ "float equality check without `.abs()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for identity operations, e.g., `x + 0`.
+ ///
+ /// ### Why is this bad?
+ /// This code can be removed without changing the
+ /// meaning. So it just obscures what's going on. Delete it mercilessly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// x / 1 + 0 * 1 - 0 | 0;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub IDENTITY_OP,
+ complexity,
+ "using identity operations, e.g., `x + 0` or `y / 1`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for division of integers
+ ///
+ /// ### Why is this bad?
+ /// When outside of some very specific algorithms,
+ /// integer division is very often a mistake because it discards the
+ /// remainder.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 3 / 2;
+ /// println!("{}", x);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let x = 3f32 / 2f32;
+ /// println!("{}", x);
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub INTEGER_DIVISION,
+ restriction,
+ "integer division may cause loss of precision"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for comparisons to NaN.
+ ///
+ /// ### Why is this bad?
+ /// NaN does not compare meaningfully to anything – not
+ /// even itself – so those comparisons are simply wrong.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1.0;
+ /// if x == f32::NAN { }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = 1.0f32;
+ /// if x.is_nan() { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CMP_NAN,
+ correctness,
+ "comparisons to `NAN`, which will always return false, probably not intended"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for conversions to owned values just for the sake
+ /// of a comparison.
+ ///
+ /// ### Why is this bad?
+ /// The comparison can operate on a reference, so creating
+ /// an owned value effectively throws it away directly afterwards, which is
+ /// needlessly consuming code and heap space.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = "foo";
+ /// # let y = String::from("foo");
+ /// if x.to_owned() == y {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = "foo";
+ /// # let y = String::from("foo");
+ /// if x == y {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CMP_OWNED,
+ perf,
+ "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for (in-)equality comparisons on floating-point
+ /// values (apart from zero), except in functions called `*eq*` (which probably
+ /// implement equality for a type involving floats).
+ ///
+ /// ### Why is this bad?
+ /// Floating point calculations are usually imprecise, so
+ /// asking if two values are *exactly* equal is asking for trouble. For a good
+ /// guide on what to do, see [the floating point
+ /// guide](http://www.floating-point-gui.de/errors/comparison).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1.2331f64;
+ /// let y = 1.2332f64;
+ ///
+ /// if y == 1.23f64 { }
+ /// if y != x {} // where both are floats
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = 1.2331f64;
+ /// # let y = 1.2332f64;
+ /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
+ /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
+ /// // let error_margin = std::f64::EPSILON;
+ /// if (y - 1.23f64).abs() < error_margin { }
+ /// if (y - x).abs() > error_margin { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_CMP,
+ pedantic,
+ "using `==` or `!=` on float values instead of comparing difference with an epsilon"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for (in-)equality comparisons on floating-point
+ /// value and constant, except in functions called `*eq*` (which probably
+ /// implement equality for a type involving floats).
+ ///
+ /// ### Why is this bad?
+ /// Floating point calculations are usually imprecise, so
+ /// asking if two values are *exactly* equal is asking for trouble. For a good
+ /// guide on what to do, see [the floating point
+ /// guide](http://www.floating-point-gui.de/errors/comparison).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: f64 = 1.0;
+ /// const ONE: f64 = 1.00;
+ ///
+ /// if x == ONE { } // where both are floats
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x: f64 = 1.0;
+ /// # const ONE: f64 = 1.00;
+ /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
+ /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
+ /// // let error_margin = std::f64::EPSILON;
+ /// if (x - ONE).abs() < error_margin { }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_CMP_CONST,
+ restriction,
+ "using `==` or `!=` on float constants instead of comparing difference with an epsilon"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for getting the remainder of a division by one or minus
+ /// one.
+ ///
+ /// ### Why is this bad?
+ /// The result for a divisor of one can only ever be zero; for
+ /// minus one it can cause panic/overflow (if the left operand is the minimal value of
+ /// the respective integer type) or results in zero. No one will write such code
+ /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
+ /// contest, it's probably a bad idea. Use something more underhanded.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// let a = x % 1;
+ /// let a = x % -1;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MODULO_ONE,
+ correctness,
+ "taking a number modulo +/-1, which can either panic/overflow or always returns 0"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for modulo arithmetic.
+ ///
+ /// ### Why is this bad?
+ /// The results of modulo (%) operation might differ
+ /// depending on the language, when negative numbers are involved.
+ /// If you interop with different languages it might be beneficial
+ /// to double check all places that use modulo arithmetic.
+ ///
+ /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = -17 % 3;
+ /// ```
+ #[clippy::version = "1.42.0"]
+ pub MODULO_ARITHMETIC,
+ restriction,
+ "any modulo arithmetic statement"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using
+ /// a lazy and.
+ ///
+ /// ### Why is this bad?
+ /// The bitwise operators do not support short-circuiting, so it may hinder code performance.
+ /// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold`
+ ///
+ /// ### Known problems
+ /// This lint evaluates only when the right side is determined to have no side effects. At this time, that
+ /// determination is quite conservative.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let (x,y) = (true, false);
+ /// if x & !y {} // where both x and y are booleans
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let (x,y) = (true, false);
+ /// if x && !y {}
+ /// ```
+ #[clippy::version = "1.54.0"]
+ pub NEEDLESS_BITWISE_BOOL,
+ pedantic,
+ "Boolean expressions that use bitwise rather than lazy operators"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Use `std::ptr::eq` when applicable
+ ///
+ /// ### Why is this bad?
+ /// `ptr::eq` can be used to compare `&T` references
+ /// (which coerce to `*const T` implicitly) by their address rather than
+ /// comparing the values they point to.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = &[1, 2, 3];
+ /// let b = &[1, 2, 3];
+ ///
+ /// assert!(a as *const _ as usize == b as *const _ as usize);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = &[1, 2, 3];
+ /// let b = &[1, 2, 3];
+ ///
+ /// assert!(std::ptr::eq(a, b));
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub PTR_EQ,
+ style,
+ "use `std::ptr::eq` when comparing raw pointers"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit self-assignments.
+ ///
+ /// ### Why is this bad?
+ /// Self-assignments are redundant and unlikely to be
+ /// intentional.
+ ///
+ /// ### Known problems
+ /// If expression contains any deref coercions or
+ /// indexing operations they are assumed not to have any side effects.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Event {
+ /// x: i32,
+ /// }
+ ///
+ /// fn copy_position(a: &mut Event, b: &Event) {
+ /// a.x = a.x;
+ /// }
+ /// ```
+ ///
+ /// Should be:
+ /// ```rust
+ /// struct Event {
+ /// x: i32,
+ /// }
+ ///
+ /// fn copy_position(a: &mut Event, b: &Event) {
+ /// a.x = b.x;
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub SELF_ASSIGNMENT,
+ correctness,
+ "explicit self-assignment"
+}
+
+pub struct Operators {
+ arithmetic_context: numeric_arithmetic::Context,
+ verbose_bit_mask_threshold: u64,
+}
+impl_lint_pass!(Operators => [
+ ABSURD_EXTREME_COMPARISONS,
+ ARITHMETIC_SIDE_EFFECTS,
+ INTEGER_ARITHMETIC,
+ FLOAT_ARITHMETIC,
+ ASSIGN_OP_PATTERN,
+ MISREFACTORED_ASSIGN_OP,
+ BAD_BIT_MASK,
+ INEFFECTIVE_BIT_MASK,
+ VERBOSE_BIT_MASK,
+ DOUBLE_COMPARISONS,
+ DURATION_SUBSEC,
+ EQ_OP,
+ OP_REF,
+ ERASING_OP,
+ FLOAT_EQUALITY_WITHOUT_ABS,
+ IDENTITY_OP,
+ INTEGER_DIVISION,
+ CMP_NAN,
+ CMP_OWNED,
+ FLOAT_CMP,
+ FLOAT_CMP_CONST,
+ MODULO_ONE,
+ MODULO_ARITHMETIC,
+ NEEDLESS_BITWISE_BOOL,
+ PTR_EQ,
+ SELF_ASSIGNMENT,
+]);
+impl Operators {
+ pub fn new(verbose_bit_mask_threshold: u64) -> Self {
+ Self {
+ arithmetic_context: numeric_arithmetic::Context::default(),
+ verbose_bit_mask_threshold,
+ }
+ }
+}
+impl<'tcx> LateLintPass<'tcx> for Operators {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ eq_op::check_assert(cx, e);
+ match e.kind {
+ ExprKind::Binary(op, lhs, rhs) => {
+ if !e.span.from_expansion() {
+ absurd_extreme_comparisons::check(cx, e, op.node, lhs, rhs);
+ if !(macro_with_not_op(lhs) || macro_with_not_op(rhs)) {
+ eq_op::check(cx, e, op.node, lhs, rhs);
+ op_ref::check(cx, e, op.node, lhs, rhs);
+ }
+ erasing_op::check(cx, e, op.node, lhs, rhs);
+ identity_op::check(cx, e, op.node, lhs, rhs);
+ needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
+ ptr_eq::check(cx, e, op.node, lhs, rhs);
+ }
+ self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
+ bit_mask::check(cx, e, op.node, lhs, rhs);
+ verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold);
+ double_comparison::check(cx, op.node, lhs, rhs, e.span);
+ duration_subsec::check(cx, e, op.node, lhs, rhs);
+ float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
+ integer_division::check(cx, e, op.node, lhs, rhs);
+ cmp_nan::check(cx, e, op.node, lhs, rhs);
+ cmp_owned::check(cx, op.node, lhs, rhs);
+ float_cmp::check(cx, e, op.node, lhs, rhs);
+ modulo_one::check(cx, e, op.node, rhs);
+ modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
+ },
+ ExprKind::AssignOp(op, lhs, rhs) => {
+ self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
+ misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
+ modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
+ },
+ ExprKind::Assign(lhs, rhs, _) => {
+ assign_op_pattern::check(cx, e, lhs, rhs);
+ self_assignment::check(cx, e, lhs, rhs);
+ },
+ ExprKind::Unary(op, arg) => {
+ if op == UnOp::Neg {
+ self.arithmetic_context.check_negate(cx, e, arg);
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_expr_post(&mut self, _: &LateContext<'_>, e: &Expr<'_>) {
+ self.arithmetic_context.expr_post(e.hir_id);
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) {
+ self.arithmetic_context.enter_body(cx, b);
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) {
+ self.arithmetic_context.body_post(cx, b);
+ }
+}
+
+fn macro_with_not_op(e: &Expr<'_>) -> bool {
+ if let ExprKind::Unary(_, e) = e.kind {
+ e.span.from_expansion()
+ } else {
+ false
+ }
+}
--- /dev/null
- let sugg = format!("{} {} {}", lhs_snip, op_str, rhs_snip);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+
+use super::NEEDLESS_BITWISE_BOOL;
+
+pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) {
+ let op_str = match op {
+ BinOpKind::BitAnd => "&&",
+ BinOpKind::BitOr => "||",
+ _ => return,
+ };
+ if matches!(
+ rhs.kind,
+ ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..)
+ ) && cx.typeck_results().expr_ty(e).is_bool()
+ && !rhs.can_have_side_effects()
+ {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_BITWISE_BOOL,
+ e.span,
+ "use of bitwise operator instead of lazy operator between booleans",
+ |diag| {
+ if let Some(lhs_snip) = snippet_opt(cx, lhs.span)
+ && let Some(rhs_snip) = snippet_opt(cx, rhs.span)
+ {
++ let sugg = format!("{lhs_snip} {op_str} {rhs_snip}");
+ diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
+ }
+ },
+ );
+ }
+}
--- /dev/null
- if let hir::ExprKind::Lit(lit) = &expr.kind {
- if let rustc_ast::ast::LitKind::Int(1, _) = lit.node {
- span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
- self.expr_id = Some(expr.hir_id);
- }
+use clippy_utils::consts::constant_simple;
+use clippy_utils::diagnostics::span_lint;
++use clippy_utils::is_integer_literal;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::source_map::Span;
+
+use super::{FLOAT_ARITHMETIC, INTEGER_ARITHMETIC};
+
+#[derive(Default)]
+pub struct Context {
+ expr_id: Option<hir::HirId>,
+ /// This field is used to check whether expressions are constants, such as in enum discriminants
+ /// and consts
+ const_span: Option<Span>,
+}
+impl Context {
+ fn skip_expr(&mut self, e: &hir::Expr<'_>) -> bool {
+ self.expr_id.is_some() || self.const_span.map_or(false, |span| span.contains(e.span))
+ }
+
+ pub fn check_binary<'tcx>(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ op: hir::BinOpKind,
+ l: &'tcx hir::Expr<'_>,
+ r: &'tcx hir::Expr<'_>,
+ ) {
+ if self.skip_expr(expr) {
+ return;
+ }
+ match op {
+ hir::BinOpKind::And
+ | hir::BinOpKind::Or
+ | hir::BinOpKind::BitAnd
+ | hir::BinOpKind::BitOr
+ | hir::BinOpKind::BitXor
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt => return,
+ _ => (),
+ }
+
+ let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
+ if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() {
+ match op {
+ hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind {
+ hir::ExprKind::Lit(_lit) => (),
+ hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
++ if is_integer_literal(expr, 1) {
++ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
++ self.expr_id = Some(expr.hir_id);
+ }
+ },
+ _ => {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_id = Some(expr.hir_id);
+ },
+ },
+ _ => {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_id = Some(expr.hir_id);
+ },
+ }
+ } else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
+ span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
+ self.expr_id = Some(expr.hir_id);
+ }
+ }
+
+ pub fn check_negate<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
+ if self.skip_expr(expr) {
+ return;
+ }
+ let ty = cx.typeck_results().expr_ty(arg);
+ if constant_simple(cx, cx.typeck_results(), expr).is_none() {
+ if ty.is_integral() {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_id = Some(expr.hir_id);
+ } else if ty.is_floating_point() {
+ span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
+ self.expr_id = Some(expr.hir_id);
+ }
+ }
+ }
+
+ pub fn expr_post(&mut self, id: hir::HirId) {
+ if Some(id) == self.expr_id {
+ self.expr_id = None;
+ }
+ }
+
+ pub fn enter_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);
+
+ match cx.tcx.hir().body_owner_kind(body_owner_def_id) {
+ hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => {
+ let body_span = cx.tcx.hir().span_with_body(body_owner);
+
+ if let Some(span) = self.const_span {
+ if span.contains(body_span) {
+ return;
+ }
+ }
+ self.const_span = Some(body_span);
+ },
+ hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (),
+ }
+ }
+
+ pub fn 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_with_body(body_owner);
+
+ if let Some(span) = self.const_span {
+ if span.contains(body_span) {
+ return;
+ }
+ }
+ self.const_span = None;
+ }
+}
--- /dev/null
- format!("std::ptr::eq({}, {})", left_snip, right_snip),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+
+use super::PTR_EQ;
+
+static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers";
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ op: BinOpKind,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+) {
+ if BinOpKind::Eq == op {
+ let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) {
+ (Some(lhs), Some(rhs)) => (lhs, rhs),
+ _ => (left, right),
+ };
+
+ if_chain! {
+ if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left);
+ if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right);
+ if let Some(left_snip) = snippet_opt(cx, left_var.span);
+ if let Some(right_snip) = snippet_opt(cx, right_var.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ PTR_EQ,
+ expr.span,
+ LINT_MSG,
+ "try",
++ format!("std::ptr::eq({left_snip}, {right_snip})"),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+// If the given expression is a cast to a usize, return the lhs of the cast
+// E.g., `foo as *const _ as usize` returns `foo as *const _`.
+fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize {
+ if let ExprKind::Cast(expr, _) = cast_expr.kind {
+ return Some(expr);
+ }
+ }
+ None
+}
+
+// If the given expression is a cast to a `*const` pointer, return the lhs of the cast
+// E.g., `foo as *const _` returns `foo`.
+fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() {
+ if let ExprKind::Cast(expr, _) = cast_expr.kind {
+ return Some(expr);
+ }
+ }
+ None
+}
--- /dev/null
- &format!("self-assignment of `{}` to `{}`", rhs, lhs),
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::eq_expr_value;
+use clippy_utils::source::snippet;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+
+use super::SELF_ASSIGNMENT;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>) {
+ if eq_expr_value(cx, lhs, rhs) {
+ let lhs = snippet(cx, lhs.span, "<lhs>");
+ let rhs = snippet(cx, rhs.span, "<rhs>");
+ span_lint(
+ cx,
+ SELF_ASSIGNMENT,
+ e.span,
++ &format!("self-assignment of `{rhs}` to `{lhs}`"),
+ );
+ }
+}
--- /dev/null
- format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::Sugg;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+
+use super::VERBOSE_BIT_MASK;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ op: BinOpKind,
+ left: &'tcx Expr<'_>,
+ right: &'tcx Expr<'_>,
+ threshold: u64,
+) {
+ if BinOpKind::Eq == op
+ && let ExprKind::Binary(op1, left1, right1) = &left.kind
+ && BinOpKind::BitAnd == op1.node
+ && let ExprKind::Lit(lit) = &right1.kind
+ && let LitKind::Int(n, _) = lit.node
+ && let ExprKind::Lit(lit1) = &right.kind
+ && let LitKind::Int(0, _) = lit1.node
+ && n.leading_zeros() == n.count_zeros()
+ && n > u128::from(threshold)
+ {
+ span_lint_and_then(
+ cx,
+ VERBOSE_BIT_MASK,
+ e.span,
+ "bit mask could be simplified with a call to `trailing_zeros`",
+ |diag| {
+ let sugg = Sugg::hir(cx, left1, "...").maybe_par();
+ diag.span_suggestion(
+ e.span,
+ "try",
++ format!("{sugg}.trailing_zeros() >= {}", n.count_ones()),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+}
--- /dev/null
- can_move_expr_to_closure, eager_or_lazy, higher, in_constant, is_else_clause, is_lang_ctor, peel_blocks,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{
- struct OptionOccurence {
++ 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/_ => {..}
+/// }
+/// ```
- fn try_get_option_occurence<'tcx>(
++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 {
+ ""
+ }
+ )
+}
+
- ) -> Option<OptionOccurence> {
++fn try_get_option_occurrence<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &Pat<'tcx>,
+ expr: &Expr<'_>,
+ if_then: &'tcx Expr<'_>,
+ if_else: &'tcx Expr<'_>,
- return Some(OptionOccurence {
++) -> 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 => (),
+ }
+ }
+ }
+
- some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir_with_macro_callsite(cx, some_body, "..")),
++ return Some(OptionOccurrence {
+ option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
+ method_sugg: method_sugg.to_string(),
- if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk) {
++ 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 {
- /// this function returns an `OptionOccurence` struct with details if
++ 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
- fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurence> {
++/// this function returns an `OptionOccurrence` struct with details if
+/// this construct is found, or None if this construct is not found.
- return try_get_option_occurence(cx, let_pat, let_expr, if_then, if_else);
++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) {
- fn detect_option_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurence> {
++ return try_get_option_occurrence(cx, let_pat, let_expr, if_then, if_else);
+ }
+ }
+ None
+}
+
- return try_get_option_occurence(cx, let_pat, ex, if_then, if_else);
++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) {
- PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
++ 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 arms.len() == 2 {
+ return if is_none_or_err_arm(cx, &arms[1]) {
+ Some((arms[0].pat, arms[0].body, arms[1].body))
+ } else if is_none_or_err_arm(cx, &arms[0]) {
+ Some((arms[1].pat, arms[1].body, arms[0].body))
+ } else {
+ None
+ };
+ }
+ None
+}
+
+fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ match arm.pat.kind {
- is_lang_ctor(cx, qpath, ResultErr) && matches!(first_pat.kind, PatKind::Wild)
++ 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
- use clippy_utils::visitors::expr_visitor_no_bodies;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::macros::root_macro_call_first_node;
+use clippy_utils::return_ty;
+use clippy_utils::ty::is_type_diagnostic_item;
- use rustc_hir::intravisit::{FnKind, Visitor};
++use clippy_utils::visitors::{for_each_expr, Descend};
++use core::ops::ControlFlow;
+use rustc_hir as hir;
- expr_visitor_no_bodies(|expr| {
- let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return true };
++use rustc_hir::intravisit::FnKind;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `panic!`, `unimplemented!`, `todo!`, `unreachable!` or assertions in a function of type result.
+ ///
+ /// ### Why is this bad?
+ /// For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided.
+ ///
+ /// ### Known problems
+ /// Functions called from a function returning a `Result` may invoke a panicking macro. This is not checked.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn result_with_panic() -> Result<bool, String>
+ /// {
+ /// panic!("error");
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn result_without_panic() -> Result<bool, String> {
+ /// Err(String::from("error"))
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub PANIC_IN_RESULT_FN,
+ restriction,
+ "functions of type `Result<..>` that contain `panic!()`, `todo!()`, `unreachable()`, `unimplemented()` or assertion"
+}
+
+declare_lint_pass!(PanicInResultFn => [PANIC_IN_RESULT_FN]);
+
+impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: FnKind<'tcx>,
+ _: &'tcx hir::FnDecl<'tcx>,
+ body: &'tcx hir::Body<'tcx>,
+ span: Span,
+ hir_id: hir::HirId,
+ ) {
+ if !matches!(fn_kind, FnKind::Closure) && is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) {
+ lint_impl_body(cx, span, body);
+ }
+ }
+}
+
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
+ let mut panics = Vec::new();
- return false;
++ let _: Option<!> = for_each_expr(body.value, |e| {
++ let Some(macro_call) = root_macro_call_first_node(cx, e) else {
++ return ControlFlow::Continue(Descend::Yes);
++ };
+ if matches!(
+ cx.tcx.item_name(macro_call.def_id).as_str(),
+ "unimplemented" | "unreachable" | "panic" | "todo" | "assert" | "assert_eq" | "assert_ne"
+ ) {
+ panics.push(macro_call.span);
- true
- })
- .visit_expr(body.value);
++ ControlFlow::Continue(Descend::No)
++ } else {
++ ControlFlow::Continue(Descend::Yes)
+ }
++ });
+ if !panics.is_empty() {
+ span_lint_and_then(
+ cx,
+ PANIC_IN_RESULT_FN,
+ impl_span,
+ "used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`",
+ move |diag| {
+ diag.help(
+ "`unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing",
+ );
+ diag.span_note(panics, "return Err() instead of panicking");
+ },
+ );
+ }
+}
--- /dev/null
- diagnostics::span_lint_and_sugg, is_lang_ctor, peel_hir_expr_refs, peel_ref_operators, sugg,
+use clippy_utils::{
- && matches!(&peel_hir_expr_refs(expr).0.kind,
- ExprKind::Path(p) if is_lang_ctor(cx, p, LangItem::OptionNone))
++ 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.64.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
- &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
+use std::cmp;
+use std::iter;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
+use clippy_utils::{is_self, is_self_ty};
+use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_ast::attr;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, PointerCast};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, RegionKind};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::{sym, Span};
+use rustc_target::spec::abi::Abi;
+use rustc_target::spec::Target;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by reference, where
+ /// the argument type is `Copy` and small enough to be more efficient to always
+ /// pass by value.
+ ///
+ /// ### Why is this bad?
+ /// In many calling conventions instances of structs will
+ /// be passed through registers if they fit into two or less general purpose
+ /// registers.
+ ///
+ /// ### Known problems
+ /// This lint is target register size dependent, it is
+ /// limited to 32-bit to try and reduce portability problems between 32 and
+ /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit
+ /// will be different.
+ ///
+ /// The configuration option `trivial_copy_size_limit` can be set to override
+ /// this limit for a project.
+ ///
+ /// This lint attempts to allow passing arguments by reference if a reference
+ /// to that argument is returned. This is implemented by comparing the lifetime
+ /// of the argument and return value for equality. However, this can cause
+ /// false positives in cases involving multiple lifetimes that are bounded by
+ /// each other.
+ ///
+ /// Also, it does not take account of other similar cases where getting memory addresses
+ /// matters; namely, returning the pointer to the argument in question,
+ /// and passing the argument, as both references and pointers,
+ /// to a function that needs the memory address. For further details, refer to
+ /// [this issue](https://github.com/rust-lang/rust-clippy/issues/5953)
+ /// that explains a real case in which this false positive
+ /// led to an **undefined behavior** introduced with unsafe code.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// fn foo(v: &u32) {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn foo(v: u32) {}
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRIVIALLY_COPY_PASS_BY_REF,
+ pedantic,
+ "functions taking small copyable arguments by reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by value, where
+ /// the argument type is `Copy` and large enough to be worth considering
+ /// passing by reference. Does not trigger if the function is being exported,
+ /// because that might induce API breakage, if the parameter is declared as mutable,
+ /// or if the argument is a `self`.
+ ///
+ /// ### Why is this bad?
+ /// Arguments passed by value might result in an unnecessary
+ /// shallow copy, taking up more space in the stack and requiring a call to
+ /// `memcpy`, which can be expensive.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[derive(Clone, Copy)]
+ /// struct TooLarge([u8; 2048]);
+ ///
+ /// fn foo(v: TooLarge) {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #[derive(Clone, Copy)]
+ /// # struct TooLarge([u8; 2048]);
+ /// fn foo(v: &TooLarge) {}
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub LARGE_TYPES_PASSED_BY_VALUE,
+ pedantic,
+ "functions taking large arguments by value"
+}
+
+#[derive(Copy, Clone)]
+pub struct PassByRefOrValue {
+ ref_min_size: u64,
+ value_max_size: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl<'tcx> PassByRefOrValue {
+ pub fn new(
+ ref_min_size: Option<u64>,
+ value_max_size: u64,
+ avoid_breaking_exported_api: bool,
+ target: &Target,
+ ) -> Self {
+ let ref_min_size = ref_min_size.unwrap_or_else(|| {
+ let bit_width = u64::from(target.pointer_width);
+ // Cap the calculated bit width at 32-bits to reduce
+ // portability problems between 32 and 64-bit targets
+ let bit_width = cmp::min(bit_width, 32);
+ #[expect(clippy::integer_division)]
+ let byte_width = bit_width / 8;
+ // Use a limit of 2 times the register byte width
+ byte_width * 2
+ });
+
+ Self {
+ ref_min_size,
+ value_max_size,
+ avoid_breaking_exported_api,
+ }
+ }
+
+ fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
+ if self.avoid_breaking_exported_api && cx.access_levels.is_exported(def_id) {
+ return;
+ }
+
+ let fn_sig = cx.tcx.fn_sig(def_id);
+ let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
+
+ // Gather all the lifetimes found in the output type which may affect whether
+ // `TRIVIALLY_COPY_PASS_BY_REF` should be linted.
+ let mut output_regions = FxHashSet::default();
+ for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow<!> {
+ output_regions.insert(region);
+ ControlFlow::Continue(())
+ });
+
+ for (index, (input, ty)) in iter::zip(
+ decl.inputs,
+ fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)),
+ )
+ .enumerate()
+ {
+ // All spans generated from a proc-macro invocation are the same...
+ match span {
+ Some(s) if s == input.span => continue,
+ _ => (),
+ }
+
+ match *ty.skip_binder().kind() {
+ ty::Ref(lt, ty, Mutability::Not) => {
+ match lt.kind() {
+ RegionKind::ReLateBound(index, region)
+ if index.as_u32() == 0 && output_regions.contains(®ion) =>
+ {
+ continue;
+ },
+ // Early bound regions on functions are either from the containing item, are bounded by another
+ // lifetime, or are used as a bound for a type or lifetime.
+ RegionKind::ReEarlyBound(..) => continue,
+ _ => (),
+ }
+
+ let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
+ if is_copy(cx, ty)
+ && let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
+ && size <= self.ref_min_size
+ && let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
+ {
+ if let Some(typeck) = cx.maybe_typeck_results() {
+ // Don't lint if an unsafe pointer is created.
+ // TODO: Limit the check only to unsafe pointers to the argument (or part of the argument)
+ // which escape the current function.
+ if typeck.node_types().iter().any(|(_, &ty)| ty.is_unsafe_ptr())
+ || typeck
+ .adjustments()
+ .iter()
+ .flat_map(|(_, a)| a)
+ .any(|a| matches!(a.kind, Adjust::Pointer(PointerCast::UnsafeFnPointer)))
+ {
+ continue;
+ }
+ }
+ let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
+ "self".into()
+ } else {
+ snippet(cx, decl_ty.span, "_").into()
+ };
+ span_lint_and_sugg(
+ cx,
+ TRIVIALLY_COPY_PASS_BY_REF,
+ input.span,
- &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size),
++ &format!("this argument ({size} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", self.ref_min_size),
+ "consider passing by value instead",
+ value_type,
+ Applicability::Unspecified,
+ );
+ }
+ },
+
+ ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => {
+ // if function has a body and parameter is annotated with mut, ignore
+ if let Some(param) = fn_body.and_then(|body| body.params.get(index)) {
+ match param.pat.kind {
+ PatKind::Binding(BindingAnnotation::NONE, _, _, _) => {},
+ _ => continue,
+ }
+ }
+ let ty = cx.tcx.erase_late_bound_regions(ty);
+
+ if_chain! {
+ if is_copy(cx, ty);
+ if !is_self_ty(input);
+ if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
+ if size > self.value_max_size;
+ then {
+ span_lint_and_sugg(
+ cx,
+ LARGE_TYPES_PASSED_BY_VALUE,
+ input.span,
++ &format!("this argument ({size} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", self.value_max_size),
+ "consider passing by reference instead",
+ format!("&{}", snippet(cx, input.span, "_")),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ },
+
+ _ => {},
+ }
+ }
+ }
+}
+
+impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ if item.span.from_expansion() {
+ return;
+ }
+
+ if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind {
+ self.check_poly_fn(cx, item.def_id.def_id, method_sig.decl, None);
+ }
+ }
+
+ 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) => {
+ if header.abi != Abi::Rust {
+ return;
+ }
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ for a in attrs {
+ if let Some(meta_items) = a.meta_item_list() {
+ if a.has_name(sym::proc_macro_derive)
+ || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))
+ {
+ 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;
+ }
+ }
+
+ self.check_poly_fn(cx, cx.tcx.hir().local_def_id(hir_id), decl, Some(span));
+ }
+}
--- /dev/null
- format!("&{}{}", mutability.prefix_str(), ty_name),
+//! Checks for usage of `&Vec[_]` and `&String`.
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::expr_sig;
+use clippy_utils::visitors::contains_unsafe_block;
+use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
+use if_chain::if_chain;
+use rustc_errors::{Applicability, MultiSpan};
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::HirIdMap;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{
+ self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg,
+ ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
+ TraitItem, TraitItemKind, TyKind, Unsafety,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+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;
+use rustc_span::symbol::Symbol;
+use std::fmt;
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for function arguments of type `&String`, `&Vec`,
+ /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
+ /// with the appropriate `.to_owned()`/`to_string()` calls.
+ ///
+ /// ### Why is this bad?
+ /// Requiring the argument to be of the specific size
+ /// makes the function less useful for no benefit; slices in the form of `&[T]`
+ /// or `&str` usually suffice and can be obtained from other types, too.
+ ///
+ /// ### Known problems
+ /// There may be `fn(&Vec)`-typed references pointing to your function.
+ /// If you have them, you will get a compiler error after applying this lint's
+ /// suggestions. You then have the choice to undo your changes or change the
+ /// type of the reference.
+ ///
+ /// Note that if the function is part of your public interface, there may be
+ /// other crates referencing it, of which you may not be aware. Carefully
+ /// deprecate the function before applying the lint suggestions in this case.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// fn foo(&Vec<u32>) { .. }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```ignore
+ /// fn foo(&[u32]) { .. }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub PTR_ARG,
+ style,
+ "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for equality comparisons with `ptr::null`
+ ///
+ /// ### Why is this bad?
+ /// It's easier and more readable to use the inherent
+ /// `.is_null()`
+ /// method instead
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::ptr;
+ ///
+ /// if x == ptr::null {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// if x.is_null() {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub CMP_NULL,
+ style,
+ "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for functions that take immutable references and return
+ /// mutable ones. This will not trigger if no unsafe code exists as there
+ /// are multiple safe functions which will do this transformation
+ ///
+ /// To be on the conservative side, if there's at least one mutable
+ /// reference with the output lifetime, this lint will not trigger.
+ ///
+ /// ### Why is this bad?
+ /// Creating a mutable reference which can be repeatably derived from an
+ /// immutable reference is unsound as it allows creating multiple live
+ /// mutable references to the same object.
+ ///
+ /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
+ /// lead to an interim Rust release 1.15.1.
+ ///
+ /// ### Known problems
+ /// This pattern is used by memory allocators to allow allocating multiple
+ /// objects while returning mutable references to each one. So long as
+ /// different mutable references are returned each time such a function may
+ /// be safe.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// fn foo(&Foo) -> &mut Bar { .. }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub MUT_FROM_REF,
+ correctness,
+ "fns that create mutable refs from immutable ref args"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for invalid usages of `ptr::null`.
+ ///
+ /// ### Why is this bad?
+ /// This causes undefined behavior.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Undefined behavior
+ /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```ignore
+ /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub INVALID_NULL_PTR_USAGE,
+ correctness,
+ "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
+}
+
+declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
+
+impl<'tcx> LateLintPass<'tcx> for Ptr {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
+ if matches!(trait_method, TraitFn::Provided(_)) {
+ // Handled by check body.
+ return;
+ }
+
+ check_mut_from_ref(cx, sig, None);
+ for arg in check_fn_args(
+ cx,
+ cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
+ sig.decl.inputs,
+ &[],
+ )
+ .filter(|arg| arg.mutability() == Mutability::Not)
+ {
+ span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, &arg.build_msg(), |diag| {
+ diag.span_suggestion(
+ arg.span,
+ "change this to",
+ format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
+ Applicability::Unspecified,
+ );
+ });
+ }
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ let hir = cx.tcx.hir();
+ let mut parents = hir.parent_iter(body.value.hir_id);
+ let (item_id, sig, is_trait_item) = match parents.next() {
+ Some((_, Node::Item(i))) => {
+ if let ItemKind::Fn(sig, ..) = &i.kind {
+ (i.def_id, sig, false)
+ } else {
+ return;
+ }
+ },
+ Some((_, Node::ImplItem(i))) => {
+ if !matches!(parents.next(),
+ Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
+ ) {
+ return;
+ }
+ if let ImplItemKind::Fn(sig, _) = &i.kind {
+ (i.def_id, sig, false)
+ } else {
+ return;
+ }
+ },
+ Some((_, Node::TraitItem(i))) => {
+ if let TraitItemKind::Fn(sig, _) = &i.kind {
+ (i.def_id, sig, true)
+ } else {
+ return;
+ }
+ },
+ _ => return,
+ };
+
+ check_mut_from_ref(cx, sig, Some(body));
+ let decl = sig.decl;
+ let sig = cx.tcx.fn_sig(item_id).skip_binder();
+ let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
+ .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
+ .collect();
+ let results = check_ptr_arg_usage(cx, body, &lint_args);
+
+ for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
+ span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, &args.build_msg(), |diag| {
+ diag.multipart_suggestion(
+ "change this to",
+ iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
+ .chain(result.replacements.iter().map(|r| {
+ (
+ r.expr_span,
+ format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
+ )
+ }))
+ .collect(),
+ Applicability::Unspecified,
+ );
+ });
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, l, r) = expr.kind {
+ if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
+ span_lint(
+ cx,
+ CMP_NULL,
+ expr.span,
+ "comparing with null is better expressed by the `.is_null()` method",
+ );
+ }
+ } else {
+ check_invalid_ptr_usage(cx, expr);
+ }
+ }
+}
+
+fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
+ const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
+ (&paths::SLICE_FROM_RAW_PARTS, &[0]),
+ (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
+ (&paths::PTR_COPY, &[0, 1]),
+ (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
+ (&paths::PTR_READ, &[0]),
+ (&paths::PTR_READ_UNALIGNED, &[0]),
+ (&paths::PTR_READ_VOLATILE, &[0]),
+ (&paths::PTR_REPLACE, &[0]),
+ (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
+ (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
+ (&paths::PTR_SWAP, &[0, 1]),
+ (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
+ (&paths::PTR_WRITE, &[0]),
+ (&paths::PTR_WRITE_UNALIGNED, &[0]),
+ (&paths::PTR_WRITE_VOLATILE, &[0]),
+ (&paths::PTR_WRITE_BYTES, &[0]),
+ ];
+
+ 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();
+ let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
+ if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
+ .iter()
+ .find(|&&(fn_path, _)| fn_path == fun_def_path);
+ then {
+ for &arg_idx in arg_indices {
+ if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
+ span_lint_and_sugg(
+ cx,
+ INVALID_NULL_PTR_USAGE,
+ arg.span,
+ "pointer must be non-null",
+ "change this to",
+ "core::ptr::NonNull::dangling().as_ptr()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+struct PtrArgResult {
+ skip: bool,
+ replacements: Vec<PtrArgReplacement>,
+}
+
+struct PtrArgReplacement {
+ expr_span: Span,
+ self_span: Span,
+ replacement: &'static str,
+}
+
+struct PtrArg<'tcx> {
+ idx: usize,
+ emission_id: hir::HirId,
+ span: Span,
+ ty_did: DefId,
+ ty_name: Symbol,
+ method_renames: &'static [(&'static str, &'static str)],
+ ref_prefix: RefPrefix,
+ deref_ty: DerefTy<'tcx>,
+}
+impl PtrArg<'_> {
+ fn build_msg(&self) -> String {
+ format!(
+ "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
+ self.ref_prefix.mutability.prefix_str(),
+ self.ty_name,
+ self.ref_prefix.mutability.prefix_str(),
+ self.deref_ty.argless_str(),
+ )
+ }
+
+ fn mutability(&self) -> Mutability {
+ self.ref_prefix.mutability
+ }
+}
+
+struct RefPrefix {
+ lt: LifetimeName,
+ mutability: Mutability,
+}
+impl fmt::Display for RefPrefix {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use fmt::Write;
+ f.write_char('&')?;
+ match self.lt {
+ LifetimeName::Param(_, ParamName::Plain(name)) => {
+ name.fmt(f)?;
+ f.write_char(' ')?;
+ },
+ LifetimeName::Infer => f.write_str("'_ ")?,
+ LifetimeName::Static => f.write_str("'static ")?,
+ _ => (),
+ }
+ f.write_str(self.mutability.prefix_str())
+ }
+}
+
+struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
+impl fmt::Display for DerefTyDisplay<'_, '_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use std::fmt::Write;
+ match self.1 {
+ DerefTy::Str => f.write_str("str"),
+ DerefTy::Path => f.write_str("Path"),
+ DerefTy::Slice(hir_ty, ty) => {
+ f.write_char('[')?;
+ match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
+ Some(s) => f.write_str(&s)?,
+ None => ty.fmt(f)?,
+ }
+ f.write_char(']')
+ },
+ }
+ }
+}
+
+enum DerefTy<'tcx> {
+ Str,
+ Path,
+ Slice(Option<Span>, Ty<'tcx>),
+}
+impl<'tcx> DerefTy<'tcx> {
+ fn argless_str(&self) -> &'static str {
+ match *self {
+ Self::Str => "str",
+ Self::Path => "Path",
+ Self::Slice(..) => "[_]",
+ }
+ }
+
+ fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
+ DerefTyDisplay(cx, self)
+ }
+}
+
+fn check_fn_args<'cx, 'tcx: 'cx>(
+ cx: &'cx LateContext<'tcx>,
+ tys: &'tcx [Ty<'tcx>],
+ hir_tys: &'tcx [hir::Ty<'tcx>],
+ params: &'tcx [Param<'tcx>],
+) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
+ tys.iter()
+ .zip(hir_tys.iter())
+ .enumerate()
+ .filter_map(|(i, (ty, hir_ty))| {
+ if_chain! {
+ if let ty::Ref(_, ty, mutability) = *ty.kind();
+ if let ty::Adt(adt, substs) = *ty.kind();
+
+ if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
+ if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
+
+ // Check that the name as typed matches the actual name of the type.
+ // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
+ if let [.., name] = path.segments;
+ if cx.tcx.item_name(adt.did()) == name.ident.name;
+
+ then {
+ let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id);
+ let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
+ Some(sym::Vec) => (
+ [("clone", ".to_owned()")].as_slice(),
+ DerefTy::Slice(
+ name.args
+ .and_then(|args| args.args.first())
+ .and_then(|arg| if let GenericArg::Type(ty) = arg {
+ Some(ty.span)
+ } else {
+ None
+ }),
+ substs.type_at(0),
+ ),
+ ),
+ Some(sym::String) => (
+ [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
+ DerefTy::Str,
+ ),
+ Some(sym::PathBuf) => (
+ [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
+ DerefTy::Path,
+ ),
+ Some(sym::Cow) if mutability == Mutability::Not => {
+ let ty_name = name.args
+ .and_then(|args| {
+ args.args.iter().find_map(|a| match a {
+ GenericArg::Type(x) => Some(x),
+ _ => None,
+ })
+ })
+ .and_then(|arg| snippet_opt(cx, arg.span))
+ .unwrap_or_else(|| substs.type_at(1).to_string());
+ span_lint_hir_and_then(
+ cx,
+ PTR_ARG,
+ emission_id,
+ hir_ty.span,
+ "using a reference to `Cow` is not recommended",
+ |diag| {
+ diag.span_suggestion(
+ hir_ty.span,
+ "change this to",
- if let TyKind::Rptr(ref lt, ref m) = ty.kind {
++ format!("&{}{ty_name}", mutability.prefix_str()),
+ Applicability::Unspecified,
+ );
+ }
+ );
+ return None;
+ },
+ _ => return None,
+ };
+ return Some(PtrArg {
+ idx: i,
+ emission_id,
+ span: hir_ty.span,
+ ty_did: adt.did(),
+ ty_name: name.ident.name,
+ method_renames,
+ ref_prefix: RefPrefix {
+ lt: lt.name,
+ mutability,
+ },
+ deref_ty,
+ });
+ }
+ }
+ None
+ })
+}
+
+fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
+ if let FnRetTy::Return(ty) = sig.decl.output
+ && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
+ {
+ let out_region = cx.tcx.named_region(out.hir_id);
+ let args: Option<Vec<_>> = sig
+ .decl
+ .inputs
+ .iter()
+ .filter_map(get_rptr_lm)
+ .filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region)
+ .map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span))
+ .collect();
+ if let Some(args) = args
+ && !args.is_empty()
+ && body.map_or(true, |body| {
+ sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, body.value)
+ })
+ {
+ span_lint_and_then(
+ cx,
+ MUT_FROM_REF,
+ ty.span,
+ "mutable borrow from immutable input(s)",
+ |diag| {
+ let ms = MultiSpan::from_spans(args);
+ diag.span_note(ms, "immutable borrow here");
+ },
+ );
+ }
+ }
+}
+
+#[expect(clippy::too_many_lines)]
+fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ /// Map from a local id to which argument it came from (index into `Self::args` and
+ /// `Self::results`)
+ bindings: HirIdMap<usize>,
+ /// The arguments being checked.
+ args: &'cx [PtrArg<'tcx>],
+ /// The results for each argument (len should match args.len)
+ results: Vec<PtrArgResult>,
+ /// The number of arguments which can't be linted. Used to return early.
+ skip_count: usize,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.skip_count == self.args.len() {
+ return;
+ }
+
+ // Check if this is local we care about
+ let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
+ Some(&i) => i,
+ None => return walk_expr(self, e),
+ };
+ let args = &self.args[args_idx];
+ let result = &mut self.results[args_idx];
+
+ // Helper function to handle early returns.
+ let mut set_skip_flag = || {
+ if !result.skip {
+ self.skip_count += 1;
+ }
+ result.skip = true;
+ };
+
+ match get_expr_use_or_unification_node(self.cx.tcx, e) {
+ Some((Node::Stmt(_), _)) => (),
+ Some((Node::Local(l), _)) => {
+ // Only trace simple bindings. e.g `let x = y;`
+ if let PatKind::Binding(BindingAnnotation::NONE, id, _, None) = l.pat.kind {
+ self.bindings.insert(id, args_idx);
+ } else {
+ set_skip_flag();
+ }
+ },
+ Some((Node::Expr(e), child_id)) => match e.kind {
+ ExprKind::Call(f, expr_args) => {
+ let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
+ if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| {
+ match *ty.skip_binder().peel_refs().kind() {
+ ty::Param(_) => true,
+ ty::Adt(def, _) => def.did() == args.ty_did,
+ _ => false,
+ }
+ }) {
+ // Passed to a function taking the non-dereferenced type.
+ set_skip_flag();
+ }
+ },
+ ExprKind::MethodCall(name, self_arg, expr_args, _) => {
+ let i = std::iter::once(self_arg)
+ .chain(expr_args.iter())
+ .position(|arg| arg.hir_id == child_id)
+ .unwrap_or(0);
+ if i == 0 {
+ // Check if the method can be renamed.
+ let name = name.ident.as_str();
+ if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
+ result.replacements.push(PtrArgReplacement {
+ expr_span: e.span,
+ self_span: self_arg.span,
+ replacement,
+ });
+ return;
+ }
+ }
+
+ let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
+ x
+ } else {
+ set_skip_flag();
+ return;
+ };
+
+ match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
+ ty::Param(_) => {
+ set_skip_flag();
+ },
+ // If the types match check for methods which exist on both types. e.g. `Vec::len` and
+ // `slice::len`
+ ty::Adt(def, _) if def.did() == args.ty_did => {
+ set_skip_flag();
+ },
+ _ => (),
+ }
+ },
+ // Indexing is fine for currently supported types.
+ ExprKind::Index(e, _) if e.hir_id == child_id => (),
+ _ => set_skip_flag(),
+ },
+ _ => set_skip_flag(),
+ }
+ }
+ }
+
+ let mut skip_count = 0;
+ let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
+ let mut v = V {
+ cx,
+ bindings: args
+ .iter()
+ .enumerate()
+ .filter_map(|(i, arg)| {
+ let param = &body.params[arg.idx];
+ match param.pat.kind {
+ PatKind::Binding(BindingAnnotation::NONE, id, _, None)
+ if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
+ {
+ Some((id, i))
+ },
+ _ => {
+ skip_count += 1;
+ results[i].skip = true;
+ None
+ },
+ }
+ })
+ .collect(),
+ args,
+ results,
+ skip_count,
+ };
+ v.visit_expr(body.value);
+ v.results
+}
+
+fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
++ if let TyKind::Rptr(lt, ref m) = ty.kind {
+ Some((lt, m.mutbl, ty.span))
+ } else {
+ None
+ }
+}
+
+fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(pathexp, []) = expr.kind {
+ path_def_id(cx, pathexp).map_or(false, |id| {
+ matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))
+ })
+ } else {
+ false
+ }
+}
--- /dev/null
- let msg = format!("use of `{}` with a `usize` casted to an `isize`", method);
+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 (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) {
+ Some(call_arg) => call_arg,
+ None => return,
+ };
+
+ // Check if the argument to the method call is a cast from usize
+ let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) {
+ Some(cast_lhs_expr) => cast_lhs_expr,
+ None => return,
+ };
+
- Some(format!("{}.{}({})", receiver, method.suggestion(), cast_lhs))
++ 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_usize<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize
+}
+
+// Is the type of the expression a raw pointer?
+fn is_expr_ty_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ cx.typeck_results().expr_ty(expr).is_unsafe_ptr()
+}
+
+fn build_suggestion<'tcx>(
+ cx: &LateContext<'tcx>,
+ 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
- eq_expr_value, get_parent_node, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks,
- peel_blocks_with_stmt,
+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::{
- &'hir QPath<'hir>,
++ 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(
- format!("Some({}?)", receiver_str)
++ 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.at(caller.span), 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!("{}{}?;", receiver_str, if by_ref { ".as_ref()" } else { "" })
++ format!("Some({receiver_str}?)")
+ } else {
+ return;
+ }
+ } else {
- let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_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);
- "{}{}?{}",
- receiver_str,
++ 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!(
- IfBlockType::IfLet(qpath, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => {
++ "{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,
+ }
+ },
- is_lang_ctor(cx, qpath, OptionSome)
++ 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_lang_ctor(cx, qpath, ResultOk)
++ 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_lang_ctor(cx, qpath, ResultErr)
++ (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)))
- sym::Option => is_lang_ctor(cx, qpath, OptionNone),
++ || is_res_lang_ctor(cx, res, ResultErr)
+ && expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym))
+ },
+ _ => 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 {
- check_is_none_or_err_and_early_return(cx, expr);
- check_if_let_some_or_err_and_early_return(cx, expr);
++ 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
- &format!("manual `{}::contains` implementation", range_type),
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::higher;
+use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, path_to_local};
+use if_chain::if_chain;
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, HirId};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::{Span, Spanned};
+use std::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for exclusive ranges where 1 is added to the
+ /// upper bound, e.g., `x..(y+1)`.
+ ///
+ /// ### Why is this bad?
+ /// The code is more readable with an inclusive range
+ /// like `x..=y`.
+ ///
+ /// ### Known problems
+ /// Will add unnecessary pair of parentheses when the
+ /// expression is not wrapped in a pair but starts with an opening parenthesis
+ /// and ends with a closing one.
+ /// I.e., `let _ = (f()+1)..(f()+1)` results in `let _ = ((f()+1)..=f())`.
+ ///
+ /// Also in many cases, inclusive ranges are still slower to run than
+ /// exclusive ranges, because they essentially add an extra branch that
+ /// LLVM may fail to hoist out of the loop.
+ ///
+ /// This will cause a warning that cannot be fixed if the consumer of the
+ /// range only accepts a specific range type, instead of the generic
+ /// `RangeBounds` trait
+ /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 0;
+ /// # let y = 1;
+ /// for i in x..(y+1) {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = 0;
+ /// # let y = 1;
+ /// for i in x..=y {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_PLUS_ONE,
+ pedantic,
+ "`x..(y+1)` reads better as `x..=y`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for inclusive ranges where 1 is subtracted from
+ /// the upper bound, e.g., `x..=(y-1)`.
+ ///
+ /// ### Why is this bad?
+ /// The code is more readable with an exclusive range
+ /// like `x..y`.
+ ///
+ /// ### Known problems
+ /// This will cause a warning that cannot be fixed if
+ /// the consumer of the range only accepts a specific range type, instead of
+ /// the generic `RangeBounds` trait
+ /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 0;
+ /// # let y = 1;
+ /// for i in x..=(y-1) {
+ /// // ..
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # let x = 0;
+ /// # let y = 1;
+ /// for i in x..y {
+ /// // ..
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_MINUS_ONE,
+ pedantic,
+ "`x..=(y-1)` reads better as `x..y`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for range expressions `x..y` where both `x` and `y`
+ /// are constant and `x` is greater or equal to `y`.
+ ///
+ /// ### Why is this bad?
+ /// Empty ranges yield no values so iterating them is a no-op.
+ /// Moreover, trying to use a reversed range to index a slice will panic at run-time.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// fn main() {
+ /// (10..=0).for_each(|x| println!("{}", x));
+ ///
+ /// let arr = [1, 2, 3, 4, 5];
+ /// let sub = &arr[3..1];
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// (0..=10).rev().for_each(|x| println!("{}", x));
+ ///
+ /// let arr = [1, 2, 3, 4, 5];
+ /// let sub = &arr[1..3];
+ /// }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub REVERSED_EMPTY_RANGES,
+ correctness,
+ "reversing the limits of range expressions, resulting in empty ranges"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions like `x >= 3 && x < 8` that could
+ /// be more readably expressed as `(3..8).contains(x)`.
+ ///
+ /// ### Why is this bad?
+ /// `contains` expresses the intent better and has less
+ /// failure modes (such as fencepost errors or using `||` instead of `&&`).
+ ///
+ /// ### Example
+ /// ```rust
+ /// // given
+ /// let x = 6;
+ ///
+ /// assert!(x >= 3 && x < 8);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ ///# let x = 6;
+ /// assert!((3..8).contains(&x));
+ /// ```
+ #[clippy::version = "1.49.0"]
+ pub MANUAL_RANGE_CONTAINS,
+ style,
+ "manually reimplementing {`Range`, `RangeInclusive`}`::contains`"
+}
+
+pub struct Ranges {
+ msrv: Option<RustcVersion>,
+}
+
+impl Ranges {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Ranges => [
+ RANGE_PLUS_ONE,
+ RANGE_MINUS_ONE,
+ REVERSED_EMPTY_RANGES,
+ MANUAL_RANGE_CONTAINS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Ranges {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, l, r) = expr.kind {
+ if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) {
+ check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
+ }
+ }
+
+ check_exclusive_range_plus_one(cx, expr);
+ check_inclusive_range_minus_one(cx, expr);
+ check_reversed_empty_range(cx, expr);
+ }
+ extract_msrv_attr!(LateContext);
+}
+
+fn check_possible_range_contains(
+ cx: &LateContext<'_>,
+ op: BinOpKind,
+ left: &Expr<'_>,
+ right: &Expr<'_>,
+ expr: &Expr<'_>,
+ span: Span,
+) {
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ let combine_and = match op {
+ BinOpKind::And | BinOpKind::BitAnd => true,
+ BinOpKind::Or | BinOpKind::BitOr => false,
+ _ => return,
+ };
+ // value, name, order (higher/lower), inclusiveness
+ if let (Some(l), Some(r)) = (check_range_bounds(cx, left), check_range_bounds(cx, right)) {
+ // we only lint comparisons on the same name and with different
+ // direction
+ if l.id != r.id || l.ord == r.ord {
+ return;
+ }
+ let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l.expr), &l.val, &r.val);
+ if combine_and && ord == Some(r.ord) {
+ // order lower bound and upper bound
+ let (l_span, u_span, l_inc, u_inc) = if r.ord == Ordering::Less {
+ (l.val_span, r.val_span, l.inc, r.inc)
+ } else {
+ (r.val_span, l.val_span, r.inc, l.inc)
+ };
+ // we only lint inclusive lower bounds
+ if !l_inc {
+ return;
+ }
+ let (range_type, range_op) = if u_inc {
+ ("RangeInclusive", "..=")
+ } else {
+ ("Range", "..")
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let name = snippet_with_applicability(cx, l.name_span, "_", &mut applicability);
+ let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability);
+ let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability);
+ let space = if lo.ends_with('.') { " " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RANGE_CONTAINS,
+ span,
- format!("({}{}{}{}).contains(&{})", lo, space, range_op, hi, name),
++ &format!("manual `{range_type}::contains` implementation"),
+ "use",
- &format!("manual `!{}::contains` implementation", range_type),
++ format!("({lo}{space}{range_op}{hi}).contains(&{name})"),
+ applicability,
+ );
+ } else if !combine_and && ord == Some(l.ord) {
+ // `!_.contains(_)`
+ // order lower bound and upper bound
+ let (l_span, u_span, l_inc, u_inc) = if l.ord == Ordering::Less {
+ (l.val_span, r.val_span, l.inc, r.inc)
+ } else {
+ (r.val_span, l.val_span, r.inc, l.inc)
+ };
+ if l_inc {
+ return;
+ }
+ let (range_type, range_op) = if u_inc {
+ ("Range", "..")
+ } else {
+ ("RangeInclusive", "..=")
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let name = snippet_with_applicability(cx, l.name_span, "_", &mut applicability);
+ let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability);
+ let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability);
+ let space = if lo.ends_with('.') { " " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RANGE_CONTAINS,
+ span,
- format!("!({}{}{}{}).contains(&{})", lo, space, range_op, hi, name),
++ &format!("manual `!{range_type}::contains` implementation"),
+ "use",
- format!("({}..={})", start, end),
++ format!("!({lo}{space}{range_op}{hi}).contains(&{name})"),
+ applicability,
+ );
+ }
+ }
+
+ // If the LHS is the same operator, we have to recurse to get the "real" RHS, since they have
+ // the same operator precedence
+ if_chain! {
+ if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind;
+ if op == lhs_op.node;
+ let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent());
+ if let Some(snip) = &snippet_opt(cx, new_span);
+ // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
+ if snip.matches('(').count() == snip.matches(')').count();
+ then {
+ check_possible_range_contains(cx, op, new_lhs, right, expr, new_span);
+ }
+ }
+}
+
+struct RangeBounds<'a> {
+ val: Constant,
+ expr: &'a Expr<'a>,
+ id: HirId,
+ name_span: Span,
+ val_span: Span,
+ ord: Ordering,
+ inc: bool,
+}
+
+// Takes a binary expression such as x <= 2 as input
+// Breaks apart into various pieces, such as the value of the number,
+// hir id of the variable, and direction/inclusiveness of the operator
+fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<RangeBounds<'a>> {
+ if let ExprKind::Binary(ref op, l, r) = ex.kind {
+ let (inclusive, ordering) = match op.node {
+ BinOpKind::Gt => (false, Ordering::Greater),
+ BinOpKind::Ge => (true, Ordering::Greater),
+ BinOpKind::Lt => (false, Ordering::Less),
+ BinOpKind::Le => (true, Ordering::Less),
+ _ => return None,
+ };
+ if let Some(id) = path_to_local(l) {
+ if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
+ return Some(RangeBounds {
+ val: c,
+ expr: r,
+ id,
+ name_span: l.span,
+ val_span: r.span,
+ ord: ordering,
+ inc: inclusive,
+ });
+ }
+ } else if let Some(id) = path_to_local(r) {
+ if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
+ return Some(RangeBounds {
+ val: c,
+ expr: l,
+ id,
+ name_span: r.span,
+ val_span: l.span,
+ ord: ordering.reverse(),
+ inc: inclusive,
+ });
+ }
+ }
+ }
+ None
+}
+
+// exclusive range plus one: `x..(y+1)`
+fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if expr.span.can_be_used_for_suggestions();
+ if let Some(higher::Range {
+ start,
+ end: Some(end),
+ limits: RangeLimits::HalfOpen
+ }) = higher::Range::hir(expr);
+ if let Some(y) = y_plus_one(cx, end);
+ then {
+ let span = expr.span;
+ span_lint_and_then(
+ cx,
+ RANGE_PLUS_ONE,
+ span,
+ "an inclusive range would be more readable",
+ |diag| {
+ let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string());
+ let end = Sugg::hir(cx, y, "y").maybe_par();
+ if let Some(is_wrapped) = &snippet_opt(cx, span) {
+ if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') {
+ diag.span_suggestion(
+ span,
+ "use",
- format!("{}..={}", start, end),
++ format!("({start}..={end})"),
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ diag.span_suggestion(
+ span,
+ "use",
- format!("{}..{}", start, end),
++ format!("{start}..={end}"),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ },
+ );
+ }
+ }
+}
+
+// inclusive range minus one: `x..=(y-1)`
+fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if expr.span.can_be_used_for_suggestions();
+ if let Some(higher::Range { start, end: Some(end), limits: RangeLimits::Closed }) = higher::Range::hir(expr);
+ if let Some(y) = y_minus_one(cx, end);
+ then {
+ span_lint_and_then(
+ cx,
+ RANGE_MINUS_ONE,
+ expr.span,
+ "an exclusive range would be more readable",
+ |diag| {
+ let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string());
+ let end = Sugg::hir(cx, y, "y").maybe_par();
+ diag.span_suggestion(
+ expr.span,
+ "use",
- format!("({}{}{}).rev()", end_snippet, dots, start_snippet),
++ format!("{start}..{end}"),
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+ }
+ }
+}
+
+fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ fn inside_indexing_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(
+ get_parent_expr(cx, expr),
+ Some(Expr {
+ kind: ExprKind::Index(..),
+ ..
+ })
+ )
+ }
+
+ fn is_for_loop_arg(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let mut cur_expr = expr;
+ while let Some(parent_expr) = get_parent_expr(cx, cur_expr) {
+ match higher::ForLoop::hir(parent_expr) {
+ Some(higher::ForLoop { arg, .. }) if arg.hir_id == expr.hir_id => return true,
+ _ => cur_expr = parent_expr,
+ }
+ }
+
+ false
+ }
+
+ fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool {
+ match limits {
+ RangeLimits::HalfOpen => ordering != Ordering::Less,
+ RangeLimits::Closed => ordering == Ordering::Greater,
+ }
+ }
+
+ if_chain! {
+ if let Some(higher::Range { start: Some(start), end: Some(end), limits }) = higher::Range::hir(expr);
+ let ty = cx.typeck_results().expr_ty(start);
+ if let ty::Int(_) | ty::Uint(_) = ty.kind();
+ if let Some((start_idx, _)) = constant(cx, cx.typeck_results(), start);
+ if let Some((end_idx, _)) = constant(cx, cx.typeck_results(), end);
+ if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx);
+ if is_empty_range(limits, ordering);
+ then {
+ if inside_indexing_expr(cx, expr) {
+ // Avoid linting `N..N` as it has proven to be useful, see #5689 and #5628 ...
+ if ordering != Ordering::Equal {
+ span_lint(
+ cx,
+ REVERSED_EMPTY_RANGES,
+ expr.span,
+ "this range is reversed and using it to index a slice will panic at run-time",
+ );
+ }
+ // ... except in for loop arguments for backwards compatibility with `reverse_range_loop`
+ } else if ordering != Ordering::Equal || is_for_loop_arg(cx, expr) {
+ span_lint_and_then(
+ cx,
+ REVERSED_EMPTY_RANGES,
+ expr.span,
+ "this range is empty so it will yield no values",
+ |diag| {
+ if ordering != Ordering::Equal {
+ let start_snippet = snippet(cx, start.span, "_");
+ let end_snippet = snippet(cx, end.span, "_");
+ let dots = match limits {
+ RangeLimits::HalfOpen => "..",
+ RangeLimits::Closed => "..="
+ };
+
+ diag.span_suggestion(
+ expr.span,
+ "consider using the following if you are attempting to iterate over this \
+ range in reverse",
++ format!("({end_snippet}{dots}{start_snippet}).rev()"),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
+ match expr.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) => {
+ if is_integer_const(cx, lhs, 1) {
+ Some(rhs)
+ } else if is_integer_const(cx, rhs, 1) {
+ Some(lhs)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
+ match expr.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub, ..
+ },
+ lhs,
+ rhs,
+ ) if is_integer_const(cx, rhs, 1) => Some(lhs),
+ _ => None,
+ }
+}
--- /dev/null
- visitors::expr_visitor_no_bodies,
+use clippy_utils::{
+ diagnostics::{span_lint, span_lint_and_sugg},
+ higher::{get_vec_init_kind, VecInitKind},
+ source::snippet,
- use hir::{intravisit::Visitor, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
++ visitors::for_each_expr,
+};
- // finds use of `_.read(&mut v)`
- let mut read_found = false;
- let mut visitor = expr_visitor_no_bodies(|expr| {
- if let ExprKind::MethodCall(path, _self, [arg], _) = expr.kind
++use core::ops::ControlFlow;
++use hir::{Expr, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint catches reads into a zero-length `Vec`.
+ /// Especially in the case of a call to `with_capacity`, this lint warns that read
+ /// gets the number of bytes from the `Vec`'s length, not its capacity.
+ ///
+ /// ### Why is this bad?
+ /// Reading zero bytes is almost certainly not the intended behavior.
+ ///
+ /// ### Known problems
+ /// In theory, a very unusual read implementation could assign some semantic meaning
+ /// to zero-byte reads. But it seems exceptionally unlikely that code intending to do
+ /// a zero-byte read would allocate a `Vec` for it.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::io;
+ /// fn foo<F: io::Read>(mut f: F) {
+ /// let mut data = Vec::with_capacity(100);
+ /// f.read(&mut data).unwrap();
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::io;
+ /// fn foo<F: io::Read>(mut f: F) {
+ /// let mut data = Vec::with_capacity(100);
+ /// data.resize(100, 0);
+ /// f.read(&mut data).unwrap();
+ /// }
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub READ_ZERO_BYTE_VEC,
+ correctness,
+ "checks for reads into a zero-length `Vec`"
+}
+declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]);
+
+impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) {
+ for (idx, stmt) in block.stmts.iter().enumerate() {
+ if !stmt.span.from_expansion()
+ // matches `let v = Vec::new();`
+ && let StmtKind::Local(local) = stmt.kind
+ && let Local { pat, init: Some(init), .. } = local
+ && let PatKind::Binding(_, _, ident, _) = pat.kind
+ && let Some(vec_init_kind) = get_vec_init_kind(cx, init)
+ {
- read_found = true;
++ let visitor = |expr: &Expr<'_>| {
++ if let ExprKind::MethodCall(path, _, [arg], _) = expr.kind
+ && let PathSegment { ident: read_or_read_exact, .. } = *path
+ && matches!(read_or_read_exact.as_str(), "read" | "read_exact")
+ && let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind
+ && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind
+ && let [inner_seg] = inner_path.segments
+ && ident.name == inner_seg.ident.name
+ {
- !read_found
- });
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(())
+ }
- let next_stmt_span;
- if idx == block.stmts.len() - 1 {
++ };
+
- if let Some(e) = block.expr {
- visitor.visit_expr(e);
- next_stmt_span = e.span;
- } else {
- return;
- }
++ let (read_found, next_stmt_span) =
++ if let Some(next_stmt) = block.stmts.get(idx + 1) {
++ // case { .. stmt; stmt; .. }
++ (for_each_expr(next_stmt, visitor).is_some(), next_stmt.span)
++ } else if let Some(e) = block.expr {
+ // case { .. stmt; expr }
- // case { .. stmt; stmt; .. }
- let next_stmt = &block.stmts[idx + 1];
- visitor.visit_stmt(next_stmt);
- next_stmt_span = next_stmt.span;
- }
- drop(visitor);
++ (for_each_expr(e, visitor).is_some(), e.span)
+ } else {
- format!("{}.resize({}, 0); {}",
++ return
++ };
+
+ if read_found && !next_stmt_span.from_expansion() {
+ let applicability = Applicability::MaybeIncorrect;
+ match vec_init_kind {
+ VecInitKind::WithConstCapacity(len) => {
+ span_lint_and_sugg(
+ cx,
+ READ_ZERO_BYTE_VEC,
+ next_stmt_span,
+ "reading zero byte data to `Vec`",
+ "try",
- len,
++ format!("{}.resize({len}, 0); {}",
+ ident.as_str(),
+ snippet(cx, next_stmt_span, "..")
+ ),
+ applicability,
+ );
+ }
+ VecInitKind::WithExprCapacity(hir_id) => {
+ let e = cx.tcx.hir().expect_expr(hir_id);
+ span_lint_and_sugg(
+ cx,
+ READ_ZERO_BYTE_VEC,
+ next_stmt_span,
+ "reading zero byte data to `Vec`",
+ "try",
+ format!("{}.resize({}, 0); {}",
+ ident.as_str(),
+ snippet(cx, e.span, ".."),
+ snippet(cx, next_stmt_span, "..")
+ ),
+ applicability,
+ );
+ }
+ _ => {
+ span_lint(
+ cx,
+ READ_ZERO_BYTE_VEC,
+ next_stmt_span,
+ "reading zero byte data to `Vec`",
+ );
+
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- &format!("pub(crate) {} inside private module", descr),
+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.def_id.def_id) == ty::Visibility::Restricted(CRATE_DEF_ID.to_def_id());
+ if !cx.access_levels.is_exported(item.def_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.def_id).descr(item.def_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.access_levels.is_exported(item.def_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
- format!("({}{}{})", reborrow_str, "*".repeat(deref_count), snip)
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::get_parent_expr;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::{is_type_lang_item, peel_mid_ty_refs};
+use if_chain::if_chain;
+use rustc_ast::util::parser::PREC_PREFIX;
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability};
+use rustc_lint::{LateContext, LateLintPass, Lint};
+use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability};
+use rustc_middle::ty::subst::GenericArg;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for redundant slicing expressions which use the full range, and
+ /// do not change the type.
+ ///
+ /// ### Why is this bad?
+ /// It unnecessarily adds complexity to the expression.
+ ///
+ /// ### Known problems
+ /// If the type being sliced has an implementation of `Index<RangeFull>`
+ /// that actually changes anything then it can't be removed. However, this would be surprising
+ /// to people reading the code and should have a note with it.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// fn get_slice(x: &[u32]) -> &[u32] {
+ /// &x[..]
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// fn get_slice(x: &[u32]) -> &[u32] {
+ /// x
+ /// }
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub REDUNDANT_SLICING,
+ complexity,
+ "redundant slicing of the whole range of a type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for slicing expressions which are equivalent to dereferencing the
+ /// value.
+ ///
+ /// ### Why is this bad?
+ /// Some people may prefer to dereference rather than slice.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![1, 2, 3];
+ /// let slice = &vec[..];
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let vec = vec![1, 2, 3];
+ /// let slice = &*vec;
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub DEREF_BY_SLICING,
+ restriction,
+ "slicing instead of dereferencing"
+}
+
+declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING, DEREF_BY_SLICING]);
+
+static REDUNDANT_SLICING_LINT: (&Lint, &str) = (REDUNDANT_SLICING, "redundant slicing of the whole range");
+static DEREF_BY_SLICING_LINT: (&Lint, &str) = (DEREF_BY_SLICING, "slicing when dereferencing would work");
+
+impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ let ctxt = expr.span.ctxt();
+ if_chain! {
+ if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
+ if addressee.span.ctxt() == ctxt;
+ if let ExprKind::Index(indexed, range) = addressee.kind;
+ if is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull);
+ then {
+ let (expr_ty, expr_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(expr));
+ let (indexed_ty, indexed_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(indexed));
+ let parent_expr = get_parent_expr(cx, expr);
+ let needs_parens_for_prefix = parent_expr.map_or(false, |parent| {
+ parent.precedence().order() > PREC_PREFIX
+ });
+ let mut app = Applicability::MachineApplicable;
+
+ let ((lint, msg), help, sugg) = if expr_ty == indexed_ty {
+ if expr_ref_count > indexed_ref_count {
+ // Indexing takes self by reference and can't return a reference to that
+ // reference as it's a local variable. The only way this could happen is if
+ // `self` contains a reference to the `Self` type. If this occurs then the
+ // lint no longer applies as it's essentially a field access, which is not
+ // redundant.
+ return;
+ }
+ let deref_count = indexed_ref_count - expr_ref_count;
+
+ let (lint, reborrow_str, help_str) = if mutability == Mutability::Mut {
+ // The slice was used to reborrow the mutable reference.
+ (DEREF_BY_SLICING_LINT, "&mut *", "reborrow the original value instead")
+ } else if matches!(
+ parent_expr,
+ Some(Expr {
+ kind: ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _),
+ ..
+ })
+ ) || cx.typeck_results().expr_adjustments(expr).first().map_or(false, |a| {
+ matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Mut { .. })))
+ }) {
+ // The slice was used to make a temporary reference.
+ (DEREF_BY_SLICING_LINT, "&*", "reborrow the original value instead")
+ } else if deref_count != 0 {
+ (DEREF_BY_SLICING_LINT, "", "dereference the original value instead")
+ } else {
+ (REDUNDANT_SLICING_LINT, "", "use the original value instead")
+ };
+
+ let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
+ let sugg = if (deref_count != 0 || !reborrow_str.is_empty()) && needs_parens_for_prefix {
- format!("{}{}{}", reborrow_str, "*".repeat(deref_count), snip)
++ format!("({reborrow_str}{}{snip})", "*".repeat(deref_count))
+ } else {
- format!("(&{}{}*{})", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip)
++ format!("{reborrow_str}{}{snip}", "*".repeat(deref_count))
+ };
+
+ (lint, help_str, sugg)
+ } else if let Some(target_id) = cx.tcx.lang_items().deref_target() {
+ if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions(
+ cx.param_env,
+ cx.tcx.mk_projection(target_id, cx.tcx.mk_substs(iter::once(GenericArg::from(indexed_ty)))),
+ ) {
+ if deref_ty == expr_ty {
+ let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
+ let sugg = if needs_parens_for_prefix {
- format!("&{}{}*{}", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip)
++ format!("(&{}{}*{snip})", mutability.prefix_str(), "*".repeat(indexed_ref_count))
+ } else {
++ format!("&{}{}*{snip}", mutability.prefix_str(), "*".repeat(indexed_ref_count))
+ };
+ (DEREF_BY_SLICING_LINT, "dereference the original value instead", sugg)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ msg,
+ help,
+ sugg,
+ app,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- let sugg = format!("&{}", snip);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{Item, ItemKind, Ty, TyKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for constants and statics with an explicit `'static` lifetime.
+ ///
+ /// ### Why is this bad?
+ /// Adding `'static` to every reference can create very
+ /// complicated types.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =
+ /// &[...]
+ /// static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =
+ /// &[...]
+ /// ```
+ /// This code can be rewritten as
+ /// ```ignore
+ /// const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]
+ /// static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]
+ /// ```
+ #[clippy::version = "1.37.0"]
+ pub REDUNDANT_STATIC_LIFETIMES,
+ style,
+ "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them."
+}
+
+pub struct RedundantStaticLifetimes {
+ msrv: Option<RustcVersion>,
+}
+
+impl RedundantStaticLifetimes {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]);
+
+impl RedundantStaticLifetimes {
+ // Recursively visit types
+ fn visit_type(ty: &Ty, cx: &EarlyContext<'_>, reason: &str) {
+ match ty.kind {
+ // Be careful of nested structures (arrays and tuples)
+ TyKind::Array(ref ty, _) | TyKind::Slice(ref ty) => {
+ Self::visit_type(ty, cx, reason);
+ },
+ TyKind::Tup(ref tup) => {
+ for tup_ty in tup {
+ Self::visit_type(tup_ty, cx, reason);
+ }
+ },
+ // This is what we are looking for !
+ TyKind::Rptr(ref optional_lifetime, ref borrow_type) => {
+ // Match the 'static lifetime
+ if let Some(lifetime) = *optional_lifetime {
+ match borrow_type.ty.kind {
+ TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => {
+ if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime {
+ let snip = snippet(cx, borrow_type.ty.span, "<type>");
++ let sugg = format!("&{snip}");
+ span_lint_and_then(
+ cx,
+ REDUNDANT_STATIC_LIFETIMES,
+ lifetime.ident.span,
+ reason,
+ |diag| {
+ diag.span_suggestion(
+ ty.span,
+ "consider removing `'static`",
+ sugg,
+ Applicability::MachineApplicable, //snippet
+ );
+ },
+ );
+ }
+ },
+ _ => {},
+ }
+ }
+ Self::visit_type(&borrow_type.ty, cx, reason);
+ },
+ _ => {},
+ }
+ }
+}
+
+impl EarlyLintPass for RedundantStaticLifetimes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if !meets_msrv(self.msrv, msrvs::STATIC_IN_CONST) {
+ return;
+ }
+
+ if !item.span.from_expansion() {
+ if let ItemKind::Const(_, ref var_type, _) = item.kind {
+ Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime");
+ // Don't check associated consts because `'static` cannot be elided on those (issue
+ // #2438)
+ }
+
+ if let ItemKind::Static(ref var_type, _, _) = item.kind {
+ Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime");
+ }
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
--- /dev/null
- span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::{LitKind, StrStyle};
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks [regex](https://crates.io/crates/regex) creation
+ /// (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`) for correct
+ /// regex syntax.
+ ///
+ /// ### Why is this bad?
+ /// This will lead to a runtime panic.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// Regex::new("(")
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub INVALID_REGEX,
+ correctness,
+ "invalid regular expressions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for trivial [regex](https://crates.io/crates/regex)
+ /// creation (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`).
+ ///
+ /// ### Why is this bad?
+ /// Matching the regex can likely be replaced by `==` or
+ /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str`
+ /// methods.
+ ///
+ /// ### Known problems
+ /// If the same regex is going to be applied to multiple
+ /// inputs, the precomputations done by `Regex` construction can give
+ /// significantly better performance than any of the `str`-based methods.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// Regex::new("^foobar")
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub TRIVIAL_REGEX,
+ nursery,
+ "trivial regular expressions"
+}
+
+declare_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]);
+
+impl<'tcx> LateLintPass<'tcx> for Regex {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(fun, [arg]) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ then {
+ if match_def_path(cx, def_id, &paths::REGEX_NEW) ||
+ match_def_path(cx, def_id, &paths::REGEX_BUILDER_NEW) {
+ check_regex(cx, arg, true);
+ } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_NEW) ||
+ match_def_path(cx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) {
+ check_regex(cx, arg, false);
+ } else if match_def_path(cx, def_id, &paths::REGEX_SET_NEW) {
+ check_set(cx, arg, true);
+ } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_SET_NEW) {
+ check_set(cx, arg, false);
+ }
+ }
+ }
+ }
+}
+
+#[must_use]
+fn str_span(base: Span, c: regex_syntax::ast::Span, offset: u8) -> Span {
+ let offset = u32::from(offset);
+ let end = base.lo() + BytePos(u32::try_from(c.end.offset).expect("offset too large") + offset);
+ let start = base.lo() + BytePos(u32::try_from(c.start.offset).expect("offset too large") + offset);
+ assert!(start <= end);
+ Span::new(start, end, base.ctxt(), base.parent())
+}
+
+fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<String> {
+ constant(cx, cx.typeck_results(), e).and_then(|(c, _)| match c {
+ Constant::Str(s) => Some(s),
+ _ => None,
+ })
+}
+
+fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> {
+ use regex_syntax::hir::Anchor::{EndText, StartText};
+ use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal};
+
+ let is_literal = |e: &[regex_syntax::hir::Hir]| e.iter().all(|e| matches!(*e.kind(), Literal(_)));
+
+ match *s.kind() {
+ Empty | Anchor(_) => Some("the regex is unlikely to be useful as it is"),
+ Literal(_) => Some("consider using `str::contains`"),
+ Alternation(ref exprs) => {
+ if exprs.iter().all(|e| e.kind().is_empty()) {
+ Some("the regex is unlikely to be useful as it is")
+ } else {
+ None
+ }
+ },
+ Concat(ref exprs) => match (exprs[0].kind(), exprs[exprs.len() - 1].kind()) {
+ (&Anchor(StartText), &Anchor(EndText)) if exprs[1..(exprs.len() - 1)].is_empty() => {
+ Some("consider using `str::is_empty`")
+ },
+ (&Anchor(StartText), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
+ Some("consider using `==` on `str`s")
+ },
+ (&Anchor(StartText), &Literal(_)) if is_literal(&exprs[1..]) => Some("consider using `str::starts_with`"),
+ (&Literal(_), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
+ Some("consider using `str::ends_with`")
+ },
+ _ if is_literal(exprs) => Some("consider using `str::contains`"),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+fn check_set<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
+ if_chain! {
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
+ if let ExprKind::Array(exprs) = expr.kind;
+ then {
+ for expr in exprs {
+ check_regex(cx, expr, utf8);
+ }
+ }
+ }
+}
+
+fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
+ let mut parser = regex_syntax::ParserBuilder::new()
+ .unicode(true)
+ .allow_invalid_utf8(!utf8)
+ .build();
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ if let LitKind::Str(ref r, style) = lit.node {
+ let r = r.as_str();
+ let offset = if let StrStyle::Raw(n) = style { 2 + n } else { 1 };
+ match parser.parse(r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(e) => {
- span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
++ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {e}"));
+ },
+ }
+ }
+ } else if let Some(r) = const_str(cx, expr) {
+ match parser.parse(&r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(e) => {
++ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {e}"));
+ },
+ }
+ }
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint_hir_and_then;
++use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
+use clippy_utils::source::{snippet_opt, snippet_with_context};
++use clippy_utils::visitors::{for_each_expr, Descend};
+use clippy_utils::{fn_def_id, path_to_local_id};
++use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
++use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `let`-bindings, which are subsequently
+ /// returned.
+ ///
+ /// ### Why is this bad?
+ /// It is just extraneous code. Remove it to make your code
+ /// more rusty.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo() -> String {
+ /// let x = String::new();
+ /// x
+ /// }
+ /// ```
+ /// instead, use
+ /// ```
+ /// fn foo() -> String {
+ /// String::new()
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub LET_AND_RETURN,
+ style,
+ "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for return statements at the end of a block.
+ ///
+ /// ### Why is this bad?
+ /// Removing the `return` and semicolon will make the code
+ /// more rusty.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
+ /// simplify to
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_RETURN,
+ style,
+ "using a return statement like `return expr;` where an expression would suffice"
+}
+
+#[derive(PartialEq, Eq, Copy, Clone)]
+enum RetReplacement {
+ Empty,
+ Block,
+ Unit,
+}
+
++impl RetReplacement {
++ fn sugg_help(self) -> &'static str {
++ match self {
++ Self::Empty => "remove `return`",
++ Self::Block => "replace `return` with an empty block",
++ Self::Unit => "replace `return` with a unit value",
++ }
++ }
++}
++
++impl ToString for RetReplacement {
++ fn to_string(&self) -> String {
++ match *self {
++ Self::Empty => "",
++ Self::Block => "{}",
++ Self::Unit => "()",
++ }
++ .to_string()
++ }
++}
++
+declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]);
+
+impl<'tcx> LateLintPass<'tcx> for Return {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ // we need both a let-binding stmt and an expr
+ if_chain! {
+ if let Some(retexpr) = block.expr;
+ if let Some(stmt) = block.stmts.iter().last();
+ if let StmtKind::Local(local) = &stmt.kind;
+ if local.ty.is_none();
+ if cx.tcx.hir().attrs(local.hir_id).is_empty();
+ if let Some(initexpr) = &local.init;
+ if let PatKind::Binding(_, local_id, _, _) = local.pat.kind;
+ if path_to_local_id(retexpr, local_id);
+ if !last_statement_borrows(cx, initexpr);
+ if !in_external_macro(cx.sess(), initexpr.span);
+ if !in_external_macro(cx.sess(), retexpr.span);
+ if !local.span.from_expansion();
+ then {
+ span_lint_hir_and_then(
+ cx,
+ LET_AND_RETURN,
+ retexpr.hir_id,
+ retexpr.span,
+ "returning the result of a `let` binding from a block",
+ |err| {
+ err.span_label(local.span, "unnecessary `let` binding");
+
+ if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
+ if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
+ snippet.push_str(" as _");
+ }
+ err.multipart_suggestion(
+ "return the expression directly",
+ vec![
+ (local.span, String::new()),
+ (retexpr.span, snippet),
+ ],
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_help(initexpr.span, "this expression can be directly returned");
+ }
+ },
+ );
+ }
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ _: &'tcx FnDecl<'tcx>,
+ body: &'tcx Body<'tcx>,
+ _: Span,
+ _: HirId,
+ ) {
+ match kind {
+ FnKind::Closure => {
+ // when returning without value in closure, replace this `return`
+ // with an empty block to prevent invalid suggestion (see #6501)
+ let replacement = if let ExprKind::Ret(None) = &body.value.kind {
+ RetReplacement::Block
+ } else {
+ RetReplacement::Empty
+ };
- check_final_expr(cx, body.value, Some(body.value.span), replacement);
++ check_final_expr(cx, body.value, vec![], replacement);
+ },
+ FnKind::ItemFn(..) | FnKind::Method(..) => {
- if let ExprKind::Block(block, _) = body.value.kind {
- check_block_return(cx, block);
- }
++ check_block_return(cx, &body.value.kind, vec![]);
+ },
+ }
+ }
+}
+
- fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) {
- if let Some(expr) = block.expr {
- check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty);
- } else if let Some(stmt) = block.stmts.iter().last() {
- match stmt.kind {
- StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
- check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
- },
- _ => (),
++// if `expr` is a block, check if there are needless returns in it
++fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, semi_spans: Vec<Span>) {
++ if let ExprKind::Block(block, _) = expr_kind {
++ if let Some(block_expr) = block.expr {
++ check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
++ } else if let Some(stmt) = block.stmts.iter().last() {
++ match stmt.kind {
++ StmtKind::Expr(expr) => {
++ check_final_expr(cx, expr, semi_spans, RetReplacement::Empty);
++ },
++ StmtKind::Semi(semi_expr) => {
++ let mut semi_spans_and_this_one = semi_spans;
++ // we only want the span containing the semicolon so we can remove it later. From `entry.rs:382`
++ if let Some(semicolon_span) = stmt.span.trim_start(semi_expr.span) {
++ semi_spans_and_this_one.push(semicolon_span);
++ check_final_expr(cx, semi_expr, semi_spans_and_this_one, RetReplacement::Empty);
++ }
++ },
++ _ => (),
++ }
+ }
+ }
+}
+
+fn check_final_expr<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
- span: Option<Span>,
++ semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
++ * needless return */
+ replacement: RetReplacement,
+) {
- match expr.kind {
++ let peeled_drop_expr = expr.peel_drop_temps();
++ match &peeled_drop_expr.kind {
+ // simple return is always "bad"
+ ExprKind::Ret(ref inner) => {
+ if cx.tcx.hir().attrs(expr.hir_id).is_empty() {
+ let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
+ if !borrows {
+ emit_return_lint(
+ cx,
- inner.map_or(expr.hir_id, |inner| inner.hir_id),
- span.expect("`else return` is not possible"),
++ peeled_drop_expr.span,
++ semi_spans,
+ inner.as_ref().map(|i| i.span),
+ replacement,
+ );
+ }
+ }
+ },
- // a whole block? check it!
- ExprKind::Block(block, _) => {
- check_block_return(cx, block);
- },
+ ExprKind::If(_, then, else_clause_opt) => {
- if let ExprKind::Block(ifblock, _) = then.kind {
- check_block_return(cx, ifblock);
- }
++ check_block_return(cx, &then.kind, semi_spans.clone());
+ if let Some(else_clause) = else_clause_opt {
- check_final_expr(cx, else_clause, None, RetReplacement::Empty);
++ check_block_return(cx, &else_clause.kind, semi_spans);
+ }
+ },
+ // a match expr, check all arms
+ // an if/if let expr, check both exprs
+ // note, if without else is going to be a type checking error anyways
+ // (except for unit type functions) so we don't match it
+ ExprKind::Match(_, arms, MatchSource::Normal) => {
+ for arm in arms.iter() {
- check_final_expr(cx, arm.body, Some(arm.body.span), RetReplacement::Unit);
++ check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
+ }
+ },
- ExprKind::DropTemps(expr) => check_final_expr(cx, expr, None, RetReplacement::Empty),
- _ => (),
++ // if it's a whole block, check it
++ other_expr_kind => check_block_return(cx, other_expr_kind, semi_spans),
+ }
+}
+
+fn emit_return_lint(
+ cx: &LateContext<'_>,
- emission_place: HirId,
+ ret_span: Span,
++ semi_spans: Vec<Span>,
+ inner_span: Option<Span>,
+ replacement: RetReplacement,
+) {
+ if ret_span.from_expansion() {
+ return;
+ }
- match inner_span {
- Some(inner_span) => {
- let mut applicability = Applicability::MachineApplicable;
- span_lint_hir_and_then(
- cx,
- NEEDLESS_RETURN,
- emission_place,
- ret_span,
- "unneeded `return` statement",
- |diag| {
- let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
- diag.span_suggestion(ret_span, "remove `return`", snippet, applicability);
- },
- );
- },
- None => match replacement {
- RetReplacement::Empty => {
- span_lint_hir_and_then(
- cx,
- NEEDLESS_RETURN,
- emission_place,
- ret_span,
- "unneeded `return` statement",
- |diag| {
- diag.span_suggestion(
- ret_span,
- "remove `return`",
- String::new(),
- Applicability::MachineApplicable,
- );
- },
- );
- },
- RetReplacement::Block => {
- span_lint_hir_and_then(
- cx,
- NEEDLESS_RETURN,
- emission_place,
- ret_span,
- "unneeded `return` statement",
- |diag| {
- diag.span_suggestion(
- ret_span,
- "replace `return` with an empty block",
- "{}".to_string(),
- Applicability::MachineApplicable,
- );
- },
- );
- },
- RetReplacement::Unit => {
- span_lint_hir_and_then(
- cx,
- NEEDLESS_RETURN,
- emission_place,
- ret_span,
- "unneeded `return` statement",
- |diag| {
- diag.span_suggestion(
- ret_span,
- "replace `return` with a unit value",
- "()".to_string(),
- Applicability::MachineApplicable,
- );
- },
- );
- },
++ let mut applicability = Applicability::MachineApplicable;
++ let return_replacement = inner_span.map_or_else(
++ || replacement.to_string(),
++ |inner_span| {
++ let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
++ snippet.to_string()
+ },
- }
++ );
++ let sugg_help = if inner_span.is_some() {
++ "remove `return`"
++ } else {
++ replacement.sugg_help()
++ };
++ span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
++ diag.span_suggestion_hidden(ret_span, sugg_help, return_replacement, applicability);
++ // for each parent statement, we need to remove the semicolon
++ for semi_stmt_span in semi_spans {
++ diag.tool_only_span_suggestion(semi_stmt_span, "remove this semicolon", "", applicability);
++ }
++ });
+}
+
+fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- let mut visitor = BorrowVisitor { cx, borrows: false };
- walk_expr(&mut visitor, expr);
- visitor.borrows
- }
-
- struct BorrowVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- borrows: bool,
- }
-
- impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if self.borrows || expr.span.from_expansion() {
- return;
- }
-
- if let Some(def_id) = fn_def_id(self.cx, expr) {
- self.borrows = self
- .cx
++ for_each_expr(expr, |e| {
++ if let Some(def_id) = fn_def_id(cx, e)
++ && cx
+ .tcx
+ .fn_sig(def_id)
- .output()
+ .skip_binder()
++ .output()
+ .walk()
- .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
++ .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
++ {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(Descend::from(!expr.span.from_expansion()))
+ }
-
- walk_expr(self, expr);
- }
++ })
++ .is_some()
+}
--- /dev/null
- &format!("existing `{}` defined here", method_name),
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::AssocKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Symbol;
+use rustc_span::Span;
+use std::collections::{BTreeMap, BTreeSet};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It lints if a struct has two methods with the same name:
+ /// one from a trait, another not from trait.
+ ///
+ /// ### Why is this bad?
+ /// Confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// trait T {
+ /// fn foo(&self) {}
+ /// }
+ ///
+ /// struct S;
+ ///
+ /// impl T for S {
+ /// fn foo(&self) {}
+ /// }
+ ///
+ /// impl S {
+ /// fn foo(&self) {}
+ /// }
+ /// ```
+ #[clippy::version = "1.57.0"]
+ pub SAME_NAME_METHOD,
+ restriction,
+ "two method with same name"
+}
+
+declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]);
+
+struct ExistingName {
+ impl_methods: BTreeMap<Symbol, (Span, HirId)>,
+ trait_methods: BTreeMap<Symbol, Vec<Span>>,
+}
+
+impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
+ #[expect(clippy::too_many_lines)]
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ let mut map = FxHashMap::<Res, ExistingName>::default();
+
+ for id in cx.tcx.hir().items() {
+ if matches!(cx.tcx.def_kind(id.def_id), DefKind::Impl)
+ && let item = cx.tcx.hir().item(id)
+ && let ItemKind::Impl(Impl {
+ items,
+ of_trait,
+ self_ty,
+ ..
+ }) = &item.kind
+ && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
+ {
+ if !map.contains_key(res) {
+ map.insert(
+ *res,
+ ExistingName {
+ impl_methods: BTreeMap::new(),
+ trait_methods: BTreeMap::new(),
+ },
+ );
+ }
+ let existing_name = map.get_mut(res).unwrap();
+
+ match of_trait {
+ Some(trait_ref) => {
+ let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
+ if let Some(Node::TraitRef(TraitRef { path, .. })) =
+ cx.tcx.hir().find(trait_ref.hir_ref_id);
+ if let Res::Def(DefKind::Trait, did) = path.res;
+ then{
+ // FIXME: if
+ // `rustc_middle::ty::assoc::AssocItems::items` is public,
+ // we can iterate its keys instead of `in_definition_order`,
+ // which's more efficient
+ cx.tcx
+ .associated_items(did)
+ .in_definition_order()
+ .filter(|assoc_item| {
+ matches!(assoc_item.kind, AssocKind::Fn)
+ })
+ .map(|assoc_item| assoc_item.name)
+ .collect()
+ }else{
+ BTreeSet::new()
+ }
+ };
+
+ let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
+ if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) {
+ span_lint_hir_and_then(
+ cx,
+ SAME_NAME_METHOD,
+ *hir_id,
+ *impl_span,
+ "method's name is the same as an existing method in a trait",
+ |diag| {
+ diag.span_note(
+ trait_method_span,
- &format!("existing `{}` defined here", method_name),
++ &format!("existing `{method_name}` defined here"),
+ );
+ },
+ );
+ }
+ if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
+ v.push(trait_method_span);
+ } else {
+ existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
+ }
+ };
+
+ for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
+ matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
+ }) {
+ let method_name = impl_item_ref.ident.name;
+ methods_in_trait.remove(&method_name);
+ check_trait_method(method_name, impl_item_ref.span);
+ }
+
+ for method_name in methods_in_trait {
+ check_trait_method(method_name, item.span);
+ }
+ },
+ None => {
+ for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
+ matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
+ }) {
+ let method_name = impl_item_ref.ident.name;
+ let impl_span = impl_item_ref.span;
+ let hir_id = impl_item_ref.id.hir_id();
+ if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
+ span_lint_hir_and_then(
+ cx,
+ SAME_NAME_METHOD,
+ hir_id,
+ impl_span,
+ "method's name is the same as an existing method in a trait",
+ |diag| {
+ // TODO should we `span_note` on every trait?
+ // iterate on trait_spans?
+ diag.span_note(
+ trait_spans[0],
++ &format!("existing `{method_name}` defined here"),
+ );
+ },
+ );
+ }
+ existing_name.impl_methods.insert(method_name, (impl_span, hir_id));
+ }
+ },
+ }
+ }
+ }
+ }
+}
--- /dev/null
- let suggestion = format!("{0};", sugg);
+use crate::rustc_lint::LintContext;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Block, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for blocks of expressions and fires if the last expression returns
+ /// `()` but is not followed by a semicolon.
+ ///
+ /// ### Why is this bad?
+ /// The semicolon might be optional but when extending the block with new
+ /// code, it doesn't require a change in previous last line.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// println!("Hello world")
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// println!("Hello world");
+ /// }
+ /// ```
+ #[clippy::version = "1.52.0"]
+ pub SEMICOLON_IF_NOTHING_RETURNED,
+ pedantic,
+ "add a semicolon if nothing is returned"
+}
+
+declare_lint_pass!(SemicolonIfNothingReturned => [SEMICOLON_IF_NOTHING_RETURNED]);
+
+impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
+ if_chain! {
+ if !block.span.from_expansion();
+ if let Some(expr) = block.expr;
+ let t_expr = cx.typeck_results().expr_ty(expr);
+ if t_expr.is_unit();
+ if let snippet = snippet_with_macro_callsite(cx, expr.span, "}");
+ if !snippet.ends_with('}') && !snippet.ends_with(';');
+ if cx.sess().source_map().is_multiline(block.span);
+ then {
+ // filter out the desugared `for` loop
+ if let ExprKind::DropTemps(..) = &expr.kind {
+ return;
+ }
+
+ let sugg = sugg::Sugg::hir_with_macro_callsite(cx, expr, "..");
++ let suggestion = format!("{sugg};");
+ span_lint_and_sugg(
+ cx,
+ SEMICOLON_IF_NOTHING_RETURNED,
+ expr.span.source_callsite(),
+ "consider adding a `;` to the last statement for consistent formatting",
+ "add a `;` here",
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{get_enclosing_block, is_expr_path_def_path, path_to_local, path_to_local_id, paths, SpanlessEq};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
- use rustc_ast::ast::LitKind;
++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;
- format!("vec![0; {}]", len_expr),
+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<'tcx>(cx: &LateContext<'tcx>, 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",
- && let ExprKind::Lit(ref lit) = fill_arg.kind
- && let LitKind::Int(0, _) = lit.node {
++ 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
- if is_expr_path_def_path(self.cx, fn_expr, &paths::ITER_REPEAT);
- if let ExprKind::Lit(ref lit) = repeat_arg.kind;
- if let LitKind::Int(0, _) = lit.node;
-
++ && 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
+use clippy_utils::diagnostics::span_lint_and_help;
++use rustc_hir::def_id::DefId;
+use rustc_hir::{def::Res, HirId, Path, PathSegment};
+use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty::DefIdTree;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, symbol::kw, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Finds items imported through `std` when available through `core`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure
+ /// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates
+ /// migrating to become `no_std` compatible.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::hash::Hasher;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use core::hash::Hasher;
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub STD_INSTEAD_OF_CORE,
+ restriction,
+ "type is imported from std when available in core"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Finds items imported through `std` when available through `alloc`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from
+ /// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful
+ /// for crates migrating to become `no_std` compatible.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::vec::Vec;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # extern crate alloc;
+ /// use alloc::vec::Vec;
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub STD_INSTEAD_OF_ALLOC,
+ restriction,
+ "type is imported from std when available in alloc"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Finds items imported through `alloc` when available through `core`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are
+ /// imported from core to ensure disabling `alloc` does not cause the crate to fail to compile. This lint
+ /// is also useful for crates migrating to become `no_std` compatible.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # extern crate alloc;
+ /// use alloc::slice::from_ref;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use core::slice::from_ref;
+ /// ```
+ #[clippy::version = "1.64.0"]
+ pub ALLOC_INSTEAD_OF_CORE,
+ restriction,
+ "type is imported from alloc when available in core"
+}
+
+#[derive(Default)]
+pub struct StdReexports {
+ // Paths which can be either a module or a macro (e.g. `std::env`) will cause this check to happen
+ // twice. First for the mod, second for the macro. This is used to avoid the lint reporting for the macro
+ // when the path could be also be used to access the module.
+ prev_span: Span,
+}
+impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]);
+
+impl<'tcx> LateLintPass<'tcx> for StdReexports {
+ fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
+ if let Res::Def(_, def_id) = path.res
+ && let Some(first_segment) = get_first_segment(path)
++ && is_stable(cx, def_id)
+ {
+ let (lint, msg, help) = match first_segment.ident.name {
+ sym::std => match cx.tcx.crate_name(def_id.krate) {
+ sym::core => (
+ STD_INSTEAD_OF_CORE,
+ "used import from `std` instead of `core`",
+ "consider importing the item from `core`",
+ ),
+ sym::alloc => (
+ STD_INSTEAD_OF_ALLOC,
+ "used import from `std` instead of `alloc`",
+ "consider importing the item from `alloc`",
+ ),
+ _ => {
+ self.prev_span = path.span;
+ return;
+ },
+ },
+ sym::alloc => {
+ if cx.tcx.crate_name(def_id.krate) == sym::core {
+ (
+ ALLOC_INSTEAD_OF_CORE,
+ "used import from `alloc` instead of `core`",
+ "consider importing the item from `core`",
+ )
+ } else {
+ self.prev_span = path.span;
+ return;
+ }
+ },
+ _ => return,
+ };
+ if path.span != self.prev_span {
+ span_lint_and_help(cx, lint, path.span, msg, None, help);
+ self.prev_span = path.span;
+ }
+ }
+ }
+}
+
+/// Returns the first named segment of a [`Path`].
+///
+/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
+/// is returned.
+fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
+ match path.segments {
+ // A global path will have PathRoot as the first segment. In this case, return the segment after.
+ [x, y, ..] if x.ident.name == kw::PathRoot => Some(y),
+ [x, ..] => Some(x),
+ _ => None,
+ }
+}
++
++/// Checks if all ancestors of `def_id` are stable, to avoid linting
++/// [unstable moves](https://github.com/rust-lang/rust/pull/95956)
++fn is_stable(cx: &LateContext<'_>, mut def_id: DefId) -> bool {
++ loop {
++ if cx
++ .tcx
++ .lookup_stability(def_id)
++ .map_or(false, |stability| stability.is_unstable())
++ {
++ return false;
++ }
++
++ match cx.tcx.opt_parent(def_id) {
++ Some(parent) => def_id = parent,
++ None => return true,
++ }
++ }
++}
--- /dev/null
- format!("Some(&{}[{}])", snippet_app, snippet(cx, right.span, "..")),
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_parent_expr, is_lint_allowed, match_function_call, method_calls, paths};
+use clippy_utils::{peel_blocks, SpanlessEq};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
+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::Spanned;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for string appends of the form `x = x + y` (without
+ /// `let`!).
+ ///
+ /// ### Why is this bad?
+ /// It's not really bad, but some people think that the
+ /// `.push_str(_)` method is more readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut x = "Hello".to_owned();
+ /// x = x + ", World";
+ ///
+ /// // More readable
+ /// x += ", World";
+ /// x.push_str(", World");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_ADD_ASSIGN,
+ pedantic,
+ "using `x = x + ..` where x is a `String` instead of `push_str()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for all instances of `x + _` where `x` is of type
+ /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
+ /// match.
+ ///
+ /// ### Why is this bad?
+ /// It's not bad in and of itself. However, this particular
+ /// `Add` implementation is asymmetric (the other operand need not be `String`,
+ /// but `x` does), while addition as mathematically defined is symmetric, also
+ /// the `String::push_str(_)` function is a perfectly good replacement.
+ /// Therefore, some dislike it and wish not to have it in their code.
+ ///
+ /// That said, other people think that string addition, having a long tradition
+ /// in other languages is actually fine, which is why we decided to make this
+ /// particular lint `allow` by default.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = "Hello".to_owned();
+ /// x + ", World";
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let mut x = "Hello".to_owned();
+ /// x.push_str(", World");
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_ADD,
+ restriction,
+ "using `x + ..` where x is a `String` instead of `push_str()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the `as_bytes` method called on string literals
+ /// that contain only ASCII characters.
+ ///
+ /// ### Why is this bad?
+ /// Byte string literals (e.g., `b"foo"`) can be used
+ /// instead. They are shorter but less discoverable than `as_bytes()`.
+ ///
+ /// ### Known problems
+ /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not
+ /// equivalent because they have different types. The former is `&[u8]`
+ /// while the latter is `&[u8; 3]`. That means in general they will have a
+ /// different set of methods and different trait implementations.
+ ///
+ /// ```compile_fail
+ /// fn f(v: Vec<u8>) {}
+ ///
+ /// f("...".as_bytes().to_owned()); // works
+ /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>
+ ///
+ /// fn g(r: impl std::io::Read) {}
+ ///
+ /// g("...".as_bytes()); // works
+ /// g(b"..."); // does not work
+ /// ```
+ ///
+ /// The actual equivalent of `"str".as_bytes()` with the same type is not
+ /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not
+ /// more readable than a function call.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let bstr = "a byte string".as_bytes();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let bstr = b"a byte string";
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_LIT_AS_BYTES,
+ nursery,
+ "calling `as_bytes` on a string literal instead of using a byte string literal"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for slice operations on strings
+ ///
+ /// ### Why is this bad?
+ /// UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse character
+ /// counts and string indices. This may lead to panics, and should warrant some test cases
+ /// containing wide UTF-8 characters. This lint is most useful in code that should avoid
+ /// panics at all costs.
+ ///
+ /// ### Known problems
+ /// Probably lots of false positives. If an index comes from a known valid position (e.g.
+ /// obtained via `char_indices` over the same string), it is totally OK.
+ ///
+ /// # Example
+ /// ```rust,should_panic
+ /// &"Ölkanne"[1..];
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub STRING_SLICE,
+ restriction,
+ "slicing a string"
+}
+
+declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN, STRING_SLICE]);
+
+impl<'tcx> LateLintPass<'tcx> for StringAdd {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), e.span) {
+ return;
+ }
+ match e.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ left,
+ _,
+ ) => {
+ if is_string(cx, left) {
+ if !is_lint_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
+ let parent = get_parent_expr(cx, e);
+ if let Some(p) = parent {
+ if let ExprKind::Assign(target, _, _) = p.kind {
+ // avoid duplicate matches
+ if SpanlessEq::new(cx).eq_expr(target, left) {
+ return;
+ }
+ }
+ }
+ }
+ span_lint(
+ cx,
+ STRING_ADD,
+ e.span,
+ "you added something to a string. Consider using `String::push_str()` instead",
+ );
+ }
+ },
+ ExprKind::Assign(target, src, _) => {
+ if is_string(cx, target) && is_add(cx, src, target) {
+ span_lint(
+ cx,
+ STRING_ADD_ASSIGN,
+ e.span,
+ "you assigned the result of adding something to this string. Consider using \
+ `String::push_str()` instead",
+ );
+ }
+ },
+ ExprKind::Index(target, _idx) => {
+ let e_ty = cx.typeck_results().expr_ty(target).peel_refs();
+ if matches!(e_ty.kind(), ty::Str) || is_type_diagnostic_item(cx, e_ty, sym::String) {
+ span_lint(
+ cx,
+ STRING_SLICE,
+ e.span,
+ "indexing into a string may panic if the index is within a UTF-8 character",
+ );
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::String)
+}
+
+fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
+ match peel_blocks(src).kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ left,
+ _,
+ ) => SpanlessEq::new(cx).eq_expr(target, left),
+ _ => false,
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check if the string is transformed to byte array and casted back to string.
+ ///
+ /// ### Why is this bad?
+ /// It's unnecessary, the string can be used directly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// &"Hello World!"[6..11];
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub STRING_FROM_UTF8_AS_BYTES,
+ complexity,
+ "casting string slices to byte slices and back"
+}
+
+// Max length a b"foo" string can take
+const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
+
+declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]);
+
+impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ use rustc_ast::LitKind;
+
+ if_chain! {
+ // Find std::str::converts::from_utf8
+ if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8);
+
+ // Find string::as_bytes
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, args) = args[0].kind;
+ if let ExprKind::Index(left, right) = args.kind;
+ let (method_names, expressions, _) = method_calls(left, 1);
+ if method_names.len() == 1;
+ if expressions.len() == 1;
+ if expressions[0].1.is_empty();
+ if method_names[0] == sym!(as_bytes);
+
+ // Check for slicer
+ if let ExprKind::Struct(QPath::LangItem(LangItem::Range, ..), _, _) = right.kind;
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let string_expression = &expressions[0].0;
+
+ let snippet_app = snippet_with_applicability(
+ cx,
+ string_expression.span, "..",
+ &mut applicability,
+ );
+
+ span_lint_and_sugg(
+ cx,
+ STRING_FROM_UTF8_AS_BYTES,
+ e.span,
+ "calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
+ "try",
- &format!("found call to `str::{}` before `str::split_whitespace`", trim_fn_name),
- &format!("remove `{}()`", trim_fn_name),
++ format!("Some(&{snippet_app}[{}])", snippet(cx, right.span, "..")),
+ applicability
+ )
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, receiver, ..) = &e.kind;
+ if path.ident.name == sym!(as_bytes);
+ if let ExprKind::Lit(lit) = &receiver.kind;
+ if let LitKind::Str(lit_content, _) = &lit.node;
+ then {
+ let callsite = snippet(cx, receiver.span.source_callsite(), r#""foo""#);
+ let mut applicability = Applicability::MachineApplicable;
+ if callsite.starts_with("include_str!") {
+ span_lint_and_sugg(
+ cx,
+ STRING_LIT_AS_BYTES,
+ e.span,
+ "calling `as_bytes()` on `include_str!(..)`",
+ "consider using `include_bytes!(..)` instead",
+ snippet_with_applicability(cx, receiver.span, r#""foo""#, &mut applicability).replacen(
+ "include_str",
+ "include_bytes",
+ 1,
+ ),
+ applicability,
+ );
+ } else if lit_content.as_str().is_ascii()
+ && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
+ && !receiver.span.from_expansion()
+ {
+ span_lint_and_sugg(
+ cx,
+ STRING_LIT_AS_BYTES,
+ e.span,
+ "calling `as_bytes()` on a string literal",
+ "consider using a byte string literal instead",
+ format!(
+ "b{}",
+ snippet_with_applicability(cx, receiver.span, r#""foo""#, &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, recv, [], _) = &e.kind;
+ if path.ident.name == sym!(into_bytes);
+ if let ExprKind::MethodCall(path, recv, [], _) = &recv.kind;
+ if matches!(path.ident.name.as_str(), "to_owned" | "to_string");
+ if let ExprKind::Lit(lit) = &recv.kind;
+ if let LitKind::Str(lit_content, _) = &lit.node;
+
+ if lit_content.as_str().is_ascii();
+ if lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT;
+ if !recv.span.from_expansion();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_sugg(
+ cx,
+ STRING_LIT_AS_BYTES,
+ e.span,
+ "calling `into_bytes()` on a string literal",
+ "consider using a byte string literal instead",
+ format!(
+ "b{}.to_vec()",
+ snippet_with_applicability(cx, recv.span, r#""..""#, &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for `.to_string()` method calls on values of type `&str`.
+ ///
+ /// ### Why is this bad?
+ /// The `to_string` method is also used on other types to convert them to a string.
+ /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better
+ /// expressed with `.to_owned()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let _ = "str".to_string();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// let _ = "str".to_owned();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STR_TO_STRING,
+ restriction,
+ "using `to_string()` on a `&str`, which should be `to_owned()`"
+}
+
+declare_lint_pass!(StrToString => [STR_TO_STRING]);
+
+impl<'tcx> LateLintPass<'tcx> for StrToString {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind;
+ if path.ident.name == sym::to_string;
+ let ty = cx.typeck_results().expr_ty(self_arg);
+ if let ty::Ref(_, ty, ..) = ty.kind();
+ if *ty.kind() == ty::Str;
+ then {
+ span_lint_and_help(
+ cx,
+ STR_TO_STRING,
+ expr.span,
+ "`to_string()` called on a `&str`",
+ None,
+ "consider using `.to_owned()`",
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for `.to_string()` method calls on values of type `String`.
+ ///
+ /// ### Why is this bad?
+ /// The `to_string` method is also used on other types to convert them to a string.
+ /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let msg = String::from("Hello World");
+ /// let _ = msg.to_string();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// let msg = String::from("Hello World");
+ /// let _ = msg.clone();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub STRING_TO_STRING,
+ restriction,
+ "using `to_string()` on a `String`, which should be `clone()`"
+}
+
+declare_lint_pass!(StringToString => [STRING_TO_STRING]);
+
+impl<'tcx> LateLintPass<'tcx> for StringToString {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind;
+ if path.ident.name == sym::to_string;
+ let ty = cx.typeck_results().expr_ty(self_arg);
+ if is_type_diagnostic_item(cx, ty, sym::String);
+ then {
+ span_lint_and_help(
+ cx,
+ STRING_TO_STRING,
+ expr.span,
+ "`to_string()` called on a `String`",
+ None,
+ "consider using `.clone()`",
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns about calling `str::trim` (or variants) before `str::split_whitespace`.
+ ///
+ /// ### Why is this bad?
+ /// `split_whitespace` already ignores leading and trailing whitespace.
+ ///
+ /// ### Example
+ /// ```rust
+ /// " A B C ".trim().split_whitespace();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// " A B C ".split_whitespace();
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub TRIM_SPLIT_WHITESPACE,
+ style,
+ "using `str::trim()` or alike before `str::split_whitespace`"
+}
+declare_lint_pass!(TrimSplitWhitespace => [TRIM_SPLIT_WHITESPACE]);
+
+impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ let tyckres = cx.typeck_results();
+ if_chain! {
+ if let ExprKind::MethodCall(path, split_recv, [], split_ws_span) = expr.kind;
+ if path.ident.name == sym!(split_whitespace);
+ if let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id);
+ if cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id);
+ if let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind;
+ if let trim_fn_name @ ("trim" | "trim_start" | "trim_end") = path.ident.name.as_str();
+ if let Some(trim_def_id) = tyckres.type_dependent_def_id(split_recv.hir_id);
+ if is_one_of_trim_diagnostic_items(cx, trim_def_id);
+ then {
+ span_lint_and_sugg(
+ cx,
+ TRIM_SPLIT_WHITESPACE,
+ trim_span.with_hi(split_ws_span.lo()),
++ &format!("found call to `str::{trim_fn_name}` before `str::split_whitespace`"),
++ &format!("remove `{trim_fn_name}()`"),
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool {
+ cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id)
+ || cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id)
+ || cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id)
+}
--- /dev/null
- format!("{}.{}().len()", val_name, method_name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::is_expr_unsafe;
+use clippy_utils::{get_parent_node, match_libc_symbol};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, UnsafeSource};
+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 for usage of `libc::strlen` on a `CString` or `CStr` value,
+ /// and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead.
+ ///
+ /// ### Why is this bad?
+ /// This avoids calling an unsafe `libc` function.
+ /// Currently, it also avoids calculating the length.
+ ///
+ /// ### Example
+ /// ```rust, ignore
+ /// use std::ffi::CString;
+ /// let cstring = CString::new("foo").expect("CString::new failed");
+ /// let len = unsafe { libc::strlen(cstring.as_ptr()) };
+ /// ```
+ /// Use instead:
+ /// ```rust, no_run
+ /// use std::ffi::CString;
+ /// let cstring = CString::new("foo").expect("CString::new failed");
+ /// let len = cstring.as_bytes().len();
+ /// ```
+ #[clippy::version = "1.55.0"]
+ pub STRLEN_ON_C_STRINGS,
+ complexity,
+ "using `libc::strlen` on a `CString` or `CStr` value, while `as_bytes().len()` or `to_bytes().len()` respectively can be used instead"
+}
+
+declare_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]);
+
+impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if !expr.span.from_expansion();
+ if let ExprKind::Call(func, [recv]) = expr.kind;
+ if let ExprKind::Path(path) = &func.kind;
+ if let Some(did) = cx.qpath_res(path, func.hir_id).opt_def_id();
+ if match_libc_symbol(cx, did, "strlen");
+ if let ExprKind::MethodCall(path, self_arg, [], _) = recv.kind;
+ if !recv.span.from_expansion();
+ if path.ident.name == sym::as_ptr;
+ then {
+ let ctxt = expr.span.ctxt();
+ let span = match get_parent_node(cx.tcx, expr.hir_id) {
+ Some(Node::Block(&Block {
+ rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), span, ..
+ }))
+ if span.ctxt() == ctxt && !is_expr_unsafe(cx, self_arg) => {
+ span
+ }
+ _ => expr.span,
+ };
+
+ let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ let mut app = Applicability::MachineApplicable;
+ let val_name = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
+ let method_name = if is_type_diagnostic_item(cx, ty, sym::cstring_type) {
+ "as_bytes"
+ } else if is_type_diagnostic_item(cx, ty, sym::CStr) {
+ "to_bytes"
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ STRLEN_ON_C_STRINGS,
+ span,
+ "using `libc::strlen` on a `CString` or `CStr` value",
+ "try this",
++ format!("{val_name}.{method_name}().len()"),
+ app,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- "{} {} {}",
- left_suggestion,
+use clippy_utils::ast_utils::{eq_id, is_useless_with_eq_exprs, IdentIter};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use core::ops::{Add, AddAssign};
+use if_chain::if_chain;
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, StmtKind};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unlikely usages of binary operators that are almost
+ /// certainly typos and/or copy/paste errors, given the other usages
+ /// of binary operators nearby.
+ ///
+ /// ### Why is this bad?
+ /// They are probably bugs and if they aren't then they look like bugs
+ /// and you should add a comment explaining why you are doing such an
+ /// odd set of operations.
+ ///
+ /// ### Known problems
+ /// There may be some false positives if you are trying to do something
+ /// unusual that happens to look like a typo.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Vec3 {
+ /// x: f64,
+ /// y: f64,
+ /// z: f64,
+ /// }
+ ///
+ /// impl Eq for Vec3 {}
+ ///
+ /// impl PartialEq for Vec3 {
+ /// fn eq(&self, other: &Self) -> bool {
+ /// // This should trigger the lint because `self.x` is compared to `other.y`
+ /// self.x == other.y && self.y == other.y && self.z == other.z
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # struct Vec3 {
+ /// # x: f64,
+ /// # y: f64,
+ /// # z: f64,
+ /// # }
+ /// // same as above except:
+ /// impl PartialEq for Vec3 {
+ /// fn eq(&self, other: &Self) -> bool {
+ /// // Note we now compare other.x to self.x
+ /// self.x == other.x && self.y == other.y && self.z == other.z
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub SUSPICIOUS_OPERATION_GROUPINGS,
+ nursery,
+ "groupings of binary operations that look suspiciously like typos"
+}
+
+declare_lint_pass!(SuspiciousOperationGroupings => [SUSPICIOUS_OPERATION_GROUPINGS]);
+
+impl EarlyLintPass for SuspiciousOperationGroupings {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let Some(binops) = extract_related_binops(&expr.kind) {
+ check_binops(cx, &binops.iter().collect::<Vec<_>>());
+
+ let mut op_types = Vec::with_capacity(binops.len());
+ // We could use a hashmap, etc. to avoid being O(n*m) here, but
+ // we want the lints to be emitted in a consistent order. Besides,
+ // m, (the number of distinct `BinOpKind`s in `binops`)
+ // will often be small, and does have an upper limit.
+ binops.iter().map(|b| b.op).for_each(|op| {
+ if !op_types.contains(&op) {
+ op_types.push(op);
+ }
+ });
+
+ for op_type in op_types {
+ let ops: Vec<_> = binops.iter().filter(|b| b.op == op_type).collect();
+
+ check_binops(cx, &ops);
+ }
+ }
+ }
+}
+
+fn check_binops(cx: &EarlyContext<'_>, binops: &[&BinaryOp<'_>]) {
+ let binop_count = binops.len();
+ if binop_count < 2 {
+ // Single binary operation expressions would likely be false
+ // positives.
+ return;
+ }
+
+ let mut one_ident_difference_count = 0;
+ let mut no_difference_info = None;
+ let mut double_difference_info = None;
+ let mut expected_ident_loc = None;
+
+ let mut paired_identifiers = FxHashSet::default();
+
+ for (i, BinaryOp { left, right, op, .. }) in binops.iter().enumerate() {
+ match ident_difference_expr(left, right) {
+ IdentDifference::NoDifference => {
+ if is_useless_with_eq_exprs(*op) {
+ // The `eq_op` lint should catch this in this case.
+ return;
+ }
+
+ no_difference_info = Some(i);
+ },
+ IdentDifference::Single(ident_loc) => {
+ one_ident_difference_count += 1;
+ if let Some(previous_expected) = expected_ident_loc {
+ if previous_expected != ident_loc {
+ // This expression doesn't match the form we're
+ // looking for.
+ return;
+ }
+ } else {
+ expected_ident_loc = Some(ident_loc);
+ }
+
+ // If there was only a single difference, all other idents
+ // must have been the same, and thus were paired.
+ for id in skip_index(IdentIter::from(*left), ident_loc.index) {
+ paired_identifiers.insert(id);
+ }
+ },
+ IdentDifference::Double(ident_loc1, ident_loc2) => {
+ double_difference_info = Some((i, ident_loc1, ident_loc2));
+ },
+ IdentDifference::Multiple | IdentDifference::NonIdent => {
+ // It's too hard to know whether this is a bug or not.
+ return;
+ },
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ if let Some(expected_loc) = expected_ident_loc {
+ match (no_difference_info, double_difference_info) {
+ (Some(i), None) => attempt_to_emit_no_difference_lint(cx, binops, i, expected_loc),
+ (None, Some((double_difference_index, ident_loc1, ident_loc2))) => {
+ if_chain! {
+ if one_ident_difference_count == binop_count - 1;
+ if let Some(binop) = binops.get(double_difference_index);
+ then {
+ let changed_loc = if ident_loc1 == expected_loc {
+ ident_loc2
+ } else if ident_loc2 == expected_loc {
+ ident_loc1
+ } else {
+ // This expression doesn't match the form we're
+ // looking for.
+ return;
+ };
+
+ if let Some(sugg) = ident_swap_sugg(
+ cx,
+ &paired_identifiers,
+ binop,
+ changed_loc,
+ &mut applicability,
+ ) {
+ emit_suggestion(
+ cx,
+ binop.span,
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn attempt_to_emit_no_difference_lint(
+ cx: &EarlyContext<'_>,
+ binops: &[&BinaryOp<'_>],
+ i: usize,
+ expected_loc: IdentLocation,
+) {
+ if let Some(binop) = binops.get(i).copied() {
+ // We need to try and figure out which identifier we should
+ // suggest using instead. Since there could be multiple
+ // replacement candidates in a given expression, and we're
+ // just taking the first one, we may get some bad lint
+ // messages.
+ let mut applicability = Applicability::MaybeIncorrect;
+
+ // We assume that the correct ident is one used elsewhere in
+ // the other binops, in a place that there was a single
+ // difference between idents before.
+ let old_left_ident = get_ident(binop.left, expected_loc);
+ let old_right_ident = get_ident(binop.right, expected_loc);
+
+ for b in skip_index(binops.iter(), i) {
+ if_chain! {
+ if let (Some(old_ident), Some(new_ident)) =
+ (old_left_ident, get_ident(b.left, expected_loc));
+ if old_ident != new_ident;
+ if let Some(sugg) = suggestion_with_swapped_ident(
+ cx,
+ binop.left,
+ expected_loc,
+ new_ident,
+ &mut applicability,
+ );
+ then {
+ emit_suggestion(
+ cx,
+ binop.span,
+ replace_left_sugg(cx, binop, &sugg, &mut applicability),
+ applicability,
+ );
+ return;
+ }
+ }
+
+ if_chain! {
+ if let (Some(old_ident), Some(new_ident)) =
+ (old_right_ident, get_ident(b.right, expected_loc));
+ if old_ident != new_ident;
+ if let Some(sugg) = suggestion_with_swapped_ident(
+ cx,
+ binop.right,
+ expected_loc,
+ new_ident,
+ &mut applicability,
+ );
+ then {
+ emit_suggestion(
+ cx,
+ binop.span,
+ replace_right_sugg(cx, binop, &sugg, &mut applicability),
+ applicability,
+ );
+ return;
+ }
+ }
+ }
+ }
+}
+
+fn emit_suggestion(cx: &EarlyContext<'_>, span: Span, sugg: String, applicability: Applicability) {
+ span_lint_and_sugg(
+ cx,
+ SUSPICIOUS_OPERATION_GROUPINGS,
+ span,
+ "this sequence of operators looks suspiciously like a bug",
+ "did you mean",
+ sugg,
+ applicability,
+ );
+}
+
+fn ident_swap_sugg(
+ cx: &EarlyContext<'_>,
+ paired_identifiers: &FxHashSet<Ident>,
+ binop: &BinaryOp<'_>,
+ location: IdentLocation,
+ applicability: &mut Applicability,
+) -> Option<String> {
+ let left_ident = get_ident(binop.left, location)?;
+ let right_ident = get_ident(binop.right, location)?;
+
+ let sugg = match (
+ paired_identifiers.contains(&left_ident),
+ paired_identifiers.contains(&right_ident),
+ ) {
+ (true, true) | (false, false) => {
+ // We don't have a good guess of what ident should be
+ // used instead, in these cases.
+ *applicability = Applicability::MaybeIncorrect;
+
+ // We arbitrarily choose one side to suggest changing,
+ // since we don't have a better guess. If the user
+ // ends up duplicating a clause, the `logic_bug` lint
+ // should catch it.
+
+ let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?;
+
+ replace_right_sugg(cx, binop, &right_suggestion, applicability)
+ },
+ (false, true) => {
+ // We haven't seen a pair involving the left one, so
+ // it's probably what is wanted.
+
+ let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?;
+
+ replace_right_sugg(cx, binop, &right_suggestion, applicability)
+ },
+ (true, false) => {
+ // We haven't seen a pair involving the right one, so
+ // it's probably what is wanted.
+ let left_suggestion = suggestion_with_swapped_ident(cx, binop.left, location, right_ident, applicability)?;
+
+ replace_left_sugg(cx, binop, &left_suggestion, applicability)
+ },
+ };
+
+ Some(sugg)
+}
+
+fn replace_left_sugg(
+ cx: &EarlyContext<'_>,
+ binop: &BinaryOp<'_>,
+ left_suggestion: &str,
+ applicability: &mut Applicability,
+) -> String {
+ format!(
- "{} {} {}",
++ "{left_suggestion} {} {}",
+ binop.op.to_string(),
+ snippet_with_applicability(cx, binop.right.span, "..", applicability),
+ )
+}
+
+fn replace_right_sugg(
+ cx: &EarlyContext<'_>,
+ binop: &BinaryOp<'_>,
+ right_suggestion: &str,
+ applicability: &mut Applicability,
+) -> String {
+ format!(
- right_suggestion,
++ "{} {} {right_suggestion}",
+ snippet_with_applicability(cx, binop.left.span, "..", applicability),
+ binop.op.to_string(),
- "{}{}{}",
+ )
+}
+
+#[derive(Clone, Debug)]
+struct BinaryOp<'exprs> {
+ op: BinOpKind,
+ span: Span,
+ left: &'exprs Expr,
+ right: &'exprs Expr,
+}
+
+impl<'exprs> BinaryOp<'exprs> {
+ fn new(op: BinOpKind, span: Span, (left, right): (&'exprs Expr, &'exprs Expr)) -> Self {
+ Self { op, span, left, right }
+ }
+}
+
+fn strip_non_ident_wrappers(expr: &Expr) -> &Expr {
+ let mut output = expr;
+ loop {
+ output = match &output.kind {
+ ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner,
+ _ => {
+ return output;
+ },
+ };
+ }
+}
+
+fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+ append_opt_vecs(chained_binops(kind), if_statement_binops(kind))
+}
+
+fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+ match kind {
+ ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind),
+ ExprKind::Paren(ref e) => if_statement_binops(&e.kind),
+ ExprKind::Block(ref block, _) => {
+ let mut output = None;
+ for stmt in &block.stmts {
+ match stmt.kind {
+ StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => {
+ output = append_opt_vecs(output, if_statement_binops(&e.kind));
+ },
+ _ => {},
+ }
+ }
+ output
+ },
+ _ => None,
+ }
+}
+
+fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
+ match (target_opt, source_opt) {
+ (Some(mut target), Some(source)) => {
+ target.reserve(source.len());
+ for op in source {
+ target.push(op);
+ }
+ Some(target)
+ },
+ (Some(v), None) | (None, Some(v)) => Some(v),
+ (None, None) => None,
+ }
+}
+
+fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+ match kind {
+ ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer),
+ ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind),
+ _ => None,
+ }
+}
+
+fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> {
+ match (&left_outer.kind, &right_outer.kind) {
+ (
+ ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e),
+ ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e),
+ ) => chained_binops_helper(left_e, right_e),
+ (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer),
+ (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => {
+ chained_binops_helper(left_outer, right_e)
+ },
+ (
+ ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right),
+ ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right),
+ ) => match (
+ chained_binops_helper(left_left, left_right),
+ chained_binops_helper(right_left, right_right),
+ ) {
+ (Some(mut left_ops), Some(right_ops)) => {
+ left_ops.reserve(right_ops.len());
+ for op in right_ops {
+ left_ops.push(op);
+ }
+ Some(left_ops)
+ },
+ (Some(mut left_ops), _) => {
+ left_ops.push(BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)));
+ Some(left_ops)
+ },
+ (_, Some(mut right_ops)) => {
+ right_ops.insert(0, BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)));
+ Some(right_ops)
+ },
+ (None, None) => Some(vec![
+ BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)),
+ BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)),
+ ]),
+ },
+ _ => None,
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
+struct IdentLocation {
+ index: usize,
+}
+
+impl Add for IdentLocation {
+ type Output = IdentLocation;
+
+ fn add(self, other: Self) -> Self::Output {
+ Self {
+ index: self.index + other.index,
+ }
+ }
+}
+
+impl AddAssign for IdentLocation {
+ fn add_assign(&mut self, other: Self) {
+ *self = *self + other;
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+enum IdentDifference {
+ NoDifference,
+ Single(IdentLocation),
+ Double(IdentLocation, IdentLocation),
+ Multiple,
+ NonIdent,
+}
+
+impl Add for IdentDifference {
+ type Output = IdentDifference;
+
+ fn add(self, other: Self) -> Self::Output {
+ match (self, other) {
+ (Self::NoDifference, output) | (output, Self::NoDifference) => output,
+ (Self::Multiple, _)
+ | (_, Self::Multiple)
+ | (Self::Double(_, _), Self::Single(_))
+ | (Self::Single(_) | Self::Double(_, _), Self::Double(_, _)) => Self::Multiple,
+ (Self::NonIdent, _) | (_, Self::NonIdent) => Self::NonIdent,
+ (Self::Single(il1), Self::Single(il2)) => Self::Double(il1, il2),
+ }
+ }
+}
+
+impl AddAssign for IdentDifference {
+ fn add_assign(&mut self, other: Self) {
+ *self = *self + other;
+ }
+}
+
+impl IdentDifference {
+ /// Returns true if learning about more differences will not change the value
+ /// of this `IdentDifference`, and false otherwise.
+ fn is_complete(&self) -> bool {
+ match self {
+ Self::NoDifference | Self::Single(_) | Self::Double(_, _) => false,
+ Self::Multiple | Self::NonIdent => true,
+ }
+ }
+}
+
+fn ident_difference_expr(left: &Expr, right: &Expr) -> IdentDifference {
+ ident_difference_expr_with_base_location(left, right, IdentLocation::default()).0
+}
+
+fn ident_difference_expr_with_base_location(
+ left: &Expr,
+ right: &Expr,
+ mut base: IdentLocation,
+) -> (IdentDifference, IdentLocation) {
+ // Ideally, this function should not use IdentIter because it should return
+ // early if the expressions have any non-ident differences. We want that early
+ // return because if without that restriction the lint would lead to false
+ // positives.
+ //
+ // But, we cannot (easily?) use a `rustc_ast::visit::Visitor`, since we need
+ // the two expressions to be walked in lockstep. And without a `Visitor`, we'd
+ // have to do all the AST traversal ourselves, which is a lot of work, since to
+ // do it properly we'd need to be able to handle more or less every possible
+ // AST node since `Item`s can be written inside `Expr`s.
+ //
+ // In practice, it seems likely that expressions, above a certain size, that
+ // happen to use the exact same idents in the exact same order, and which are
+ // not structured the same, would be rare. Therefore it seems likely that if
+ // we do only the first layer of matching ourselves and eventually fallback on
+ // IdentIter, then the output of this function will be almost always be correct
+ // in practice.
+ //
+ // If it turns out that problematic cases are more prevalent than we assume,
+ // then we should be able to change this function to do the correct traversal,
+ // without needing to change the rest of the code.
+
+ #![allow(clippy::enum_glob_use)]
+ use ExprKind::*;
+
+ match (
+ &strip_non_ident_wrappers(left).kind,
+ &strip_non_ident_wrappers(right).kind,
+ ) {
+ (Yield(_), Yield(_))
+ | (Try(_), Try(_))
+ | (Paren(_), Paren(_))
+ | (Repeat(_, _), Repeat(_, _))
+ | (Struct(_), Struct(_))
+ | (MacCall(_), MacCall(_))
+ | (InlineAsm(_), InlineAsm(_))
+ | (Ret(_), Ret(_))
+ | (Continue(_), Continue(_))
+ | (Break(_, _), Break(_, _))
+ | (AddrOf(_, _, _), AddrOf(_, _, _))
+ | (Path(_, _), Path(_, _))
+ | (Range(_, _, _), Range(_, _, _))
+ | (Index(_, _), Index(_, _))
+ | (Field(_, _), Field(_, _))
+ | (AssignOp(_, _, _), AssignOp(_, _, _))
+ | (Assign(_, _, _), Assign(_, _, _))
+ | (TryBlock(_), TryBlock(_))
+ | (Await(_), Await(_))
+ | (Async(_, _, _), Async(_, _, _))
+ | (Block(_, _), Block(_, _))
+ | (Closure(_, _, _, _, _, _, _), Closure(_, _, _, _, _, _, _))
+ | (Match(_, _), Match(_, _))
+ | (Loop(_, _), Loop(_, _))
+ | (ForLoop(_, _, _, _), ForLoop(_, _, _, _))
+ | (While(_, _, _), While(_, _, _))
+ | (If(_, _, _), If(_, _, _))
+ | (Let(_, _, _), Let(_, _, _))
+ | (Type(_, _), Type(_, _))
+ | (Cast(_, _), Cast(_, _))
+ | (Lit(_), Lit(_))
+ | (Unary(_, _), Unary(_, _))
+ | (Binary(_, _, _), Binary(_, _, _))
+ | (Tup(_), Tup(_))
+ | (MethodCall(_, _, _, _), MethodCall(_, _, _, _))
+ | (Call(_, _), Call(_, _))
+ | (ConstBlock(_), ConstBlock(_))
+ | (Array(_), Array(_))
+ | (Box(_), Box(_)) => {
+ // keep going
+ },
+ _ => {
+ return (IdentDifference::NonIdent, base);
+ },
+ }
+
+ let mut difference = IdentDifference::NoDifference;
+
+ for (left_attr, right_attr) in left.attrs.iter().zip(right.attrs.iter()) {
+ let (new_difference, new_base) =
+ ident_difference_via_ident_iter_with_base_location(left_attr, right_attr, base);
+ base = new_base;
+ difference += new_difference;
+ if difference.is_complete() {
+ return (difference, base);
+ }
+ }
+
+ let (new_difference, new_base) = ident_difference_via_ident_iter_with_base_location(left, right, base);
+ base = new_base;
+ difference += new_difference;
+
+ (difference, base)
+}
+
+fn ident_difference_via_ident_iter_with_base_location<Iterable: Into<IdentIter>>(
+ left: Iterable,
+ right: Iterable,
+ mut base: IdentLocation,
+) -> (IdentDifference, IdentLocation) {
+ // See the note in `ident_difference_expr_with_base_location` about `IdentIter`
+ let mut difference = IdentDifference::NoDifference;
+
+ let mut left_iterator = left.into();
+ let mut right_iterator = right.into();
+
+ loop {
+ match (left_iterator.next(), right_iterator.next()) {
+ (Some(left_ident), Some(right_ident)) => {
+ if !eq_id(left_ident, right_ident) {
+ difference += IdentDifference::Single(base);
+ if difference.is_complete() {
+ return (difference, base);
+ }
+ }
+ },
+ (Some(_), None) | (None, Some(_)) => {
+ return (IdentDifference::NonIdent, base);
+ },
+ (None, None) => {
+ return (difference, base);
+ },
+ }
+ base += IdentLocation { index: 1 };
+ }
+}
+
+fn get_ident(expr: &Expr, location: IdentLocation) -> Option<Ident> {
+ IdentIter::from(expr).nth(location.index)
+}
+
+fn suggestion_with_swapped_ident(
+ cx: &EarlyContext<'_>,
+ expr: &Expr,
+ location: IdentLocation,
+ new_ident: Ident,
+ applicability: &mut Applicability,
+) -> Option<String> {
+ get_ident(expr, location).and_then(|current_ident| {
+ if eq_id(current_ident, new_ident) {
+ // We never want to suggest a non-change
+ return None;
+ }
+
+ Some(format!(
- new_ident,
++ "{}{new_ident}{}",
+ snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability),
+ snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability),
+ ))
+ })
+}
+
+fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A>
+where
+ Iter: Iterator<Item = A>,
+{
+ iter.enumerate()
+ .filter_map(move |(i, a)| if i == index { None } else { Some(a) })
+}
--- /dev/null
- use rustc_hir::intravisit::{walk_expr, Visitor};
+use clippy_utils::diagnostics::span_lint;
++use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{binop_traits, trait_ref_of_method, BINOP_TRAITS, OP_ASSIGN_TRAITS};
++use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_hir as hir;
- let mut visitor = BinaryExprVisitor::default();
- visitor.visit_expr(expr);
- visitor.nb_binops
- }
-
- #[derive(Default)]
- struct BinaryExprVisitor {
- nb_binops: u32,
- }
-
- impl<'tcx> Visitor<'tcx> for BinaryExprVisitor {
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- match expr.kind {
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints for suspicious operations in impls of arithmetic operators, e.g.
+ /// subtracting elements in an Add impl.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a typo or copy-and-paste error and not intended.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// impl Add for Foo {
+ /// type Output = Foo;
+ ///
+ /// fn add(self, other: Foo) -> Foo {
+ /// Foo(self.0 - other.0)
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_ARITHMETIC_IMPL,
+ suspicious,
+ "suspicious use of operators in impl of arithmetic trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints for suspicious operations in impls of OpAssign, e.g.
+ /// subtracting elements in an AddAssign impl.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a typo or copy-and-paste error and not intended.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// impl AddAssign for Foo {
+ /// fn add_assign(&mut self, other: Foo) {
+ /// *self = *self - other;
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_OP_ASSIGN_IMPL,
+ suspicious,
+ "suspicious use of operators in impl of OpAssign trait"
+}
+
+declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ASSIGN_IMPL]);
+
+impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind;
+ if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop.node);
+ if let Ok(binop_trait_id) = cx.tcx.lang_items().require(binop_trait_lang);
+ if let Ok(op_assign_trait_id) = cx.tcx.lang_items().require(op_assign_trait_lang);
+
+ // Check for more than one binary operation in the implemented function
+ // Linting when multiple operations are involved can result in false positives
+ let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id).def_id;
+ if let hir::Node::ImplItem(impl_item) = cx.tcx.hir().get_by_def_id(parent_fn);
+ if let hir::ImplItemKind::Fn(_, body_id) = impl_item.kind;
+ let body = cx.tcx.hir().body(body_id);
+ let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id).def_id;
+ if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn);
+ let trait_id = trait_ref.path.res.def_id();
+ if ![binop_trait_id, op_assign_trait_id].contains(&trait_id);
+ if let Some(&(_, lint)) = [
+ (&BINOP_TRAITS, SUSPICIOUS_ARITHMETIC_IMPL),
+ (&OP_ASSIGN_TRAITS, SUSPICIOUS_OP_ASSIGN_IMPL),
+ ]
+ .iter()
+ .find(|&(ts, _)| ts.iter().any(|&t| Ok(trait_id) == cx.tcx.lang_items().require(t)));
+ if count_binops(body.value) == 1;
+ then {
+ span_lint(
+ cx,
+ lint,
+ binop.span,
+ &format!("suspicious use of `{}` in `{}` impl", binop.node.as_str(), cx.tcx.item_name(trait_id)),
+ );
+ }
+ }
+ }
+}
+
+fn count_binops(expr: &hir::Expr<'_>) -> u32 {
- | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
- | hir::ExprKind::AssignOp(..) => self.nb_binops += 1,
- _ => {},
++ let mut count = 0u32;
++ let _: Option<!> = for_each_expr(expr, |e| {
++ if matches!(
++ e.kind,
+ hir::ExprKind::Binary(..)
-
- walk_expr(self, expr);
- }
++ | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
++ | hir::ExprKind::AssignOp(..)
++ ) {
++ count += 1;
+ }
++ ControlFlow::Continue(())
++ });
++ count
+}
--- /dev/null
- &format!("this looks like you are swapping elements of `{}` manually", slice),
+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, 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.
+ ///
+ /// ### 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 `{}` and `{}` manually", first, second),
++ &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!("{}::mem::swap({}, {})", sugg, first.mut_addr(), second.mut_addr()),
++ &format!("this looks like you are swapping `{first}` and `{second}` manually"),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try",
- diag.note(&format!("or maybe you should use `{}::mem::replace`?", sugg));
++ format!("{sugg}::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
+ applicability,
+ );
+ if !is_xor_based {
- format!(" `{}` and `{}`", first, second),
++ 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<'_>) {
+ 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!("this looks like you are trying to swap{}", what),
++ 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,
- "{}::mem::swap({}, {})",
- sugg,
- lhs,
- rhs,
++ &format!("this looks like you are trying to swap{what}"),
+ |diag| {
+ if !what.is_empty() {
+ diag.span_suggestion(
+ span,
+ "try",
+ format!(
- &format!("or maybe you should use `{}::mem::replace`?", sugg)
++ "{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
- diag.span_suggestion(e.span, "use ptr::swap", format!("core::ptr::swap({}, {})", snip1, snip2), app);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::{match_def_path, path_def_id, paths};
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `core::mem::swap` where either parameter is derived from a pointer
+ ///
+ /// ### Why is this bad?
+ /// When at least one parameter to `swap` is derived from a pointer it may overlap with the
+ /// other. This would then lead to undefined behavior.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe fn swap(x: &[*mut u32], y: &[*mut u32]) {
+ /// for (&x, &y) in x.iter().zip(y) {
+ /// core::mem::swap(&mut *x, &mut *y);
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// unsafe fn swap(x: &[*mut u32], y: &[*mut u32]) {
+ /// for (&x, &y) in x.iter().zip(y) {
+ /// core::ptr::swap(x, y);
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.63.0"]
+ pub SWAP_PTR_TO_REF,
+ suspicious,
+ "call to `mem::swap` using pointer derived references"
+}
+declare_lint_pass!(SwapPtrToRef => [SWAP_PTR_TO_REF]);
+
+impl LateLintPass<'_> for SwapPtrToRef {
+ fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
+ if let ExprKind::Call(fn_expr, [arg1, arg2]) = e.kind
+ && let Some(fn_id) = path_def_id(cx, fn_expr)
+ && match_def_path(cx, fn_id, &paths::MEM_SWAP)
+ && let ctxt = e.span.ctxt()
+ && let (from_ptr1, arg1_span) = is_ptr_to_ref(cx, arg1, ctxt)
+ && let (from_ptr2, arg2_span) = is_ptr_to_ref(cx, arg2, ctxt)
+ && (from_ptr1 || from_ptr2)
+ {
+ span_lint_and_then(
+ cx,
+ SWAP_PTR_TO_REF,
+ e.span,
+ "call to `core::mem::swap` with a parameter derived from a raw pointer",
+ |diag| {
+ if !((from_ptr1 && arg1_span.is_none()) || (from_ptr2 && arg2_span.is_none())) {
+ let mut app = Applicability::MachineApplicable;
+ let snip1 = snippet_with_context(cx, arg1_span.unwrap_or(arg1.span), ctxt, "..", &mut app).0;
+ let snip2 = snippet_with_context(cx, arg2_span.unwrap_or(arg2.span), ctxt, "..", &mut app).0;
++ diag.span_suggestion(e.span, "use ptr::swap", format!("core::ptr::swap({snip1}, {snip2})"), app);
+ }
+ }
+ );
+ }
+ }
+}
+
+/// Checks if the expression converts a mutable pointer to a mutable reference. If it is, also
+/// returns the span of the pointer expression if it's suitable for making a suggestion.
+fn is_ptr_to_ref(cx: &LateContext<'_>, e: &Expr<'_>, ctxt: SyntaxContext) -> (bool, Option<Span>) {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, borrowed_expr) = e.kind
+ && let ExprKind::Unary(UnOp::Deref, derefed_expr) = borrowed_expr.kind
+ && cx.typeck_results().expr_ty(derefed_expr).is_unsafe_ptr()
+ {
+ (true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then_some(derefed_expr.span))
+ } else {
+ (false, None)
+ }
+}
--- /dev/null
- format!("{}.is_digit({})", char_arg_snip, radix_snip)
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::match_def_path;
+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, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.to_digit(..).is_some()` on `char`s.
+ ///
+ /// ### Why is this bad?
+ /// This is a convoluted way of checking if a `char` is a digit. It's
+ /// more straight forward to use the dedicated `is_digit` method.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let c = 'c';
+ /// # let radix = 10;
+ /// let is_digit = c.to_digit(radix).is_some();
+ /// ```
+ /// can be written as:
+ /// ```
+ /// # let c = 'c';
+ /// # let radix = 10;
+ /// let is_digit = c.is_digit(radix);
+ /// ```
+ #[clippy::version = "1.41.0"]
+ pub TO_DIGIT_IS_SOME,
+ style,
+ "`char.is_digit()` is clearer"
+}
+
+declare_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]);
+
+impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::MethodCall(is_some_path, to_digit_expr, [], _) = &expr.kind;
+ if is_some_path.ident.name.as_str() == "is_some";
+ then {
+ let match_result = match &to_digit_expr.kind {
+ hir::ExprKind::MethodCall(to_digits_path, char_arg, [radix_arg], _) => {
+ if_chain! {
+ if to_digits_path.ident.name.as_str() == "to_digit";
+ let char_arg_ty = cx.typeck_results().expr_ty_adjusted(char_arg);
+ if *char_arg_ty.kind() == ty::Char;
+ then {
+ Some((true, *char_arg, radix_arg))
+ } else {
+ None
+ }
+ }
+ }
+ hir::ExprKind::Call(to_digits_call, to_digit_args) => {
+ if_chain! {
+ if let [char_arg, radix_arg] = *to_digit_args;
+ if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind;
+ if let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id);
+ if let Some(to_digits_def_id) = to_digits_call_res.opt_def_id();
+ if match_def_path(cx, to_digits_def_id, &["core", "char", "methods", "<impl char>", "to_digit"]);
+ then {
+ Some((false, char_arg, radix_arg))
+ } else {
+ None
+ }
+ }
+ }
+ _ => None
+ };
+
+ if let Some((is_method_call, char_arg, radix_arg)) = match_result {
+ let mut applicability = Applicability::MachineApplicable;
+ let char_arg_snip = snippet_with_applicability(cx, char_arg.span, "_", &mut applicability);
+ let radix_snip = snippet_with_applicability(cx, radix_arg.span, "_", &mut applicability);
+
+ span_lint_and_sugg(
+ cx,
+ TO_DIGIT_IS_SOME,
+ expr.span,
+ "use of `.to_digit(..).is_some()`",
+ "try this",
+ if is_method_call {
- format!("char::is_digit({}, {})", char_arg_snip, radix_snip)
++ format!("{char_arg_snip}.is_digit({radix_snip})")
+ } else {
++ format!("char::is_digit({char_arg_snip}, {radix_snip})")
+ },
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- "consider combining the bounds: `{}: {}`",
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
+use clippy_utils::{SpanlessEq, SpanlessHash};
+use core::hash::{Hash, Hasher};
+use if_chain::if_chain;
+use itertools::Itertools;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::unhash::UnhashMap;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{
+ GenericArg, GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath,
+ TraitBoundModifier, TraitItem, TraitRef, Ty, TyKind, WherePredicate,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{BytePos, Span};
+use std::collections::hash_map::Entry;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about unnecessary type repetitions in trait bounds
+ ///
+ /// ### Why is this bad?
+ /// Repeating the type for every bound makes the code
+ /// less readable than combining the bounds
+ ///
+ /// ### Example
+ /// ```rust
+ /// pub fn foo<T>(t: T) where T: Copy, T: Clone {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// pub fn foo<T>(t: T) where T: Copy + Clone {}
+ /// ```
+ #[clippy::version = "1.38.0"]
+ pub TYPE_REPETITION_IN_BOUNDS,
+ nursery,
+ "types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cases where generics are being used and multiple
+ /// syntax specifications for trait bounds are used simultaneously.
+ ///
+ /// ### Why is this bad?
+ /// Duplicate bounds makes the code
+ /// less readable than specifying them only once.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn func<T: Clone + Default>(arg: T) where T: Clone + Default {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # mod hidden {
+ /// fn func<T: Clone + Default>(arg: T) {}
+ /// # }
+ ///
+ /// // or
+ ///
+ /// fn func<T>(arg: T) where T: Clone + Default {}
+ /// ```
+ ///
+ /// ```rust
+ /// fn foo<T: Default + Default>(bar: T) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn foo<T: Default>(bar: T) {}
+ /// ```
+ ///
+ /// ```rust
+ /// fn foo<T>(bar: T) where T: Default + Default {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn foo<T>(bar: T) where T: Default {}
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub TRAIT_DUPLICATION_IN_BOUNDS,
+ nursery,
+ "check if the same trait bounds are specified more than once during a generic declaration"
+}
+
+#[derive(Copy, Clone)]
+pub struct TraitBounds {
+ max_trait_bounds: u64,
+}
+
+impl TraitBounds {
+ #[must_use]
+ pub fn new(max_trait_bounds: u64) -> Self {
+ Self { max_trait_bounds }
+ }
+}
+
+impl_lint_pass!(TraitBounds => [TYPE_REPETITION_IN_BOUNDS, TRAIT_DUPLICATION_IN_BOUNDS]);
+
+impl<'tcx> LateLintPass<'tcx> for TraitBounds {
+ fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
+ self.check_type_repetition(cx, gen);
+ check_trait_bound_duplication(cx, gen);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ // special handling for self trait bounds as these are not considered generics
+ // ie. trait Foo: Display {}
+ if let Item {
+ kind: ItemKind::Trait(_, _, _, bounds, ..),
+ ..
+ } = item
+ {
+ rollup_traits(cx, bounds, "these bounds contain repeated elements");
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) {
+ let mut self_bounds_map = FxHashMap::default();
+
+ for predicate in item.generics.predicates {
+ if_chain! {
+ if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate;
+ if bound_predicate.origin != PredicateOrigin::ImplTrait;
+ if !bound_predicate.span.from_expansion();
+ if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind;
+ if let Some(PathSegment {
+ res: Res::SelfTyParam { trait_: def_id }, ..
+ }) = segments.first();
+ if let Some(
+ Node::Item(
+ Item {
+ kind: ItemKind::Trait(_, _, _, self_bounds, _),
+ .. }
+ )
+ ) = cx.tcx.hir().get_if_local(*def_id);
+ then {
+ if self_bounds_map.is_empty() {
+ for bound in self_bounds.iter() {
+ let Some((self_res, self_segments, _)) = get_trait_info_from_bound(bound) else { continue };
+ self_bounds_map.insert(self_res, self_segments);
+ }
+ }
+
+ bound_predicate
+ .bounds
+ .iter()
+ .filter_map(get_trait_info_from_bound)
+ .for_each(|(trait_item_res, trait_item_segments, span)| {
+ if let Some(self_segments) = self_bounds_map.get(&trait_item_res) {
+ if SpanlessEq::new(cx).eq_path_segments(self_segments, trait_item_segments) {
+ span_lint_and_help(
+ cx,
+ TRAIT_DUPLICATION_IN_BOUNDS,
+ span,
+ "this trait bound is already specified in trait declaration",
+ None,
+ "consider removing this trait bound",
+ );
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+}
+
+impl TraitBounds {
+ fn check_type_repetition<'tcx>(self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
+ struct SpanlessTy<'cx, 'tcx> {
+ ty: &'tcx Ty<'tcx>,
+ cx: &'cx LateContext<'tcx>,
+ }
+ impl PartialEq for SpanlessTy<'_, '_> {
+ fn eq(&self, other: &Self) -> bool {
+ let mut eq = SpanlessEq::new(self.cx);
+ eq.inter_expr().eq_ty(self.ty, other.ty)
+ }
+ }
+ impl Hash for SpanlessTy<'_, '_> {
+ fn hash<H: Hasher>(&self, h: &mut H) {
+ let mut t = SpanlessHash::new(self.cx);
+ t.hash_ty(self.ty);
+ h.write_u64(t.finish());
+ }
+ }
+ impl Eq for SpanlessTy<'_, '_> {}
+
+ if gen.span.from_expansion() {
+ return;
+ }
+ let mut map: UnhashMap<SpanlessTy<'_, '_>, Vec<&GenericBound<'_>>> = UnhashMap::default();
+ let mut applicability = Applicability::MaybeIncorrect;
+ for bound in gen.predicates {
+ if_chain! {
+ if let WherePredicate::BoundPredicate(ref p) = bound;
+ if p.origin != PredicateOrigin::ImplTrait;
+ if p.bounds.len() as u64 <= self.max_trait_bounds;
+ if !p.span.from_expansion();
+ if let Some(ref v) = map.insert(
+ SpanlessTy { ty: p.bounded_ty, cx },
+ p.bounds.iter().collect::<Vec<_>>()
+ );
+
+ then {
+ let trait_bounds = v
+ .iter()
+ .copied()
+ .chain(p.bounds.iter())
+ .filter_map(get_trait_info_from_bound)
+ .map(|(_, _, span)| snippet_with_applicability(cx, span, "..", &mut applicability))
+ .join(" + ");
+ let hint_string = format!(
- trait_bounds,
++ "consider combining the bounds: `{}: {trait_bounds}`",
+ snippet(cx, p.bounded_ty.span, "_"),
+ );
+ span_lint_and_help(
+ cx,
+ TYPE_REPETITION_IN_BOUNDS,
+ p.span,
+ "this type has already been used as a bound predicate",
+ None,
+ &hint_string,
+ );
+ }
+ }
+ }
+ }
+}
+
+fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
+ if gen.span.from_expansion() {
+ return;
+ }
+
+ // Explanation:
+ // fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z)
+ // where T: Clone + Default, { unimplemented!(); }
+ // ^^^^^^^^^^^^^^^^^^
+ // |
+ // collects each of these where clauses into a set keyed by generic name and comparable trait
+ // eg. (T, Clone)
+ let where_predicates = gen
+ .predicates
+ .iter()
+ .filter_map(|pred| {
+ if_chain! {
+ if pred.in_where_clause();
+ if let WherePredicate::BoundPredicate(bound_predicate) = pred;
+ if let TyKind::Path(QPath::Resolved(_, path)) = bound_predicate.bounded_ty.kind;
+ then {
+ return Some(
+ rollup_traits(cx, bound_predicate.bounds, "these where clauses contain repeated elements")
+ .into_iter().map(|(trait_ref, _)| (path.res, trait_ref)))
+ }
+ }
+ None
+ })
+ .flatten()
+ .collect::<FxHashSet<_>>();
+
+ // Explanation:
+ // fn bad_foo<T: Clone + Default, Z: Copy>(arg0: T, arg1: Z) ...
+ // ^^^^^^^^^^^^^^^^^^ ^^^^^^^
+ // |
+ // compare trait bounds keyed by generic name and comparable trait to collected where
+ // predicates eg. (T, Clone)
+ for predicate in gen.predicates.iter().filter(|pred| !pred.in_where_clause()) {
+ if_chain! {
+ if let WherePredicate::BoundPredicate(bound_predicate) = predicate;
+ if bound_predicate.origin != PredicateOrigin::ImplTrait;
+ if !bound_predicate.span.from_expansion();
+ if let TyKind::Path(QPath::Resolved(_, path)) = bound_predicate.bounded_ty.kind;
+ then {
+ let traits = rollup_traits(cx, bound_predicate.bounds, "these bounds contain repeated elements");
+ for (trait_ref, span) in traits {
+ let key = (path.res, trait_ref);
+ if where_predicates.contains(&key) {
+ span_lint_and_help(
+ cx,
+ TRAIT_DUPLICATION_IN_BOUNDS,
+ span,
+ "this trait bound is already specified in the where clause",
+ None,
+ "consider removing this trait bound",
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+struct ComparableTraitRef(Res, Vec<Res>);
+impl Default for ComparableTraitRef {
+ fn default() -> Self {
+ Self(Res::Err, Vec::new())
+ }
+}
+
+fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
+ if let GenericBound::Trait(t, tbm) = bound {
+ let trait_path = t.trait_ref.path;
+ let trait_span = {
+ let path_span = trait_path.span;
+ if let TraitBoundModifier::Maybe = tbm {
+ path_span.with_lo(path_span.lo() - BytePos(1)) // include the `?`
+ } else {
+ path_span
+ }
+ };
+ Some((trait_path.res, trait_path.segments, trait_span))
+ } else {
+ None
+ }
+}
+
+// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
+fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
+ ComparableTraitRef(
+ trait_ref.path.res,
+ trait_ref
+ .path
+ .segments
+ .iter()
+ .filter_map(|segment| {
+ // get trait bound type arguments
+ Some(segment.args?.args.iter().filter_map(|arg| {
+ if_chain! {
+ if let GenericArg::Type(ty) = arg;
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
+ then { return Some(path.res) }
+ }
+ None
+ }))
+ })
+ .flatten()
+ .collect(),
+ )
+}
+
+fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) -> Vec<(ComparableTraitRef, Span)> {
+ let mut map = FxHashMap::default();
+ let mut repeated_res = false;
+
+ let only_comparable_trait_refs = |bound: &GenericBound<'_>| {
+ if let GenericBound::Trait(t, _) = bound {
+ Some((into_comparable_trait_ref(&t.trait_ref), t.span))
+ } else {
+ None
+ }
+ };
+
+ let mut i = 0usize;
+ for bound in bounds.iter().filter_map(only_comparable_trait_refs) {
+ let (comparable_bound, span_direct) = bound;
+ match map.entry(comparable_bound) {
+ Entry::Occupied(_) => repeated_res = true,
+ Entry::Vacant(e) => {
+ e.insert((span_direct, i));
+ i += 1;
+ },
+ }
+ }
+
+ // Put bounds in source order
+ let mut comparable_bounds = vec![Default::default(); map.len()];
+ for (k, (v, i)) in map {
+ comparable_bounds[i] = (k, v);
+ }
+
+ if_chain! {
+ if repeated_res;
+ if let [first_trait, .., last_trait] = bounds;
+ then {
+ let all_trait_span = first_trait.span().to(last_trait.span());
+
+ let traits = comparable_bounds.iter()
+ .filter_map(|&(_, span)| snippet_opt(cx, span))
+ .collect::<Vec<_>>();
+ let traits = traits.join(" + ");
+
+ span_lint_and_sugg(
+ cx,
+ TRAIT_DUPLICATION_IN_BOUNDS,
+ all_trait_span,
+ msg,
+ "try",
+ traits,
+ Applicability::MachineApplicable
+ );
+ }
+ }
+
+ comparable_bounds
+}
--- /dev/null
- &format!(
- "transmute from a type (`{}`) to the type that it points to (`{}`)",
- from_ty, to_ty
- ),
+use super::CROSSPOINTER_TRANSMUTE;
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+/// Checks for `crosspointer_transmute` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => {
+ span_lint(
+ cx,
+ CROSSPOINTER_TRANSMUTE,
+ e.span,
- &format!(
- "transmute from a type (`{}`) to a pointer to that type (`{}`)",
- from_ty, to_ty
- ),
++ &format!("transmute from a type (`{from_ty}`) to the type that it points to (`{to_ty}`)"),
+ );
+ true
+ },
+ (_, ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => {
+ span_lint(
+ cx,
+ CROSSPOINTER_TRANSMUTE,
+ e.span,
++ &format!("transmute from a type (`{from_ty}`) to a pointer to that type (`{to_ty}`)"),
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
+use super::TRANSMUTE_FLOAT_TO_INT;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg;
+use if_chain::if_chain;
+use rustc_ast as ast;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+/// Checks for `transmute_float_to_int` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ mut arg: &'tcx Expr<'_>,
+ const_context: bool,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) if !const_context => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_FLOAT_TO_INT,
+ e.span,
- let op = format!("{}{}", sugg, float_ty.name_str()).into();
++ &format!("transmute from a `{from_ty}` to a `{to_ty}`"),
+ |diag| {
+ let mut sugg = sugg::Sugg::hir(cx, arg, "..");
+
+ if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind {
+ arg = inner_expr;
+ }
+
+ if_chain! {
+ // if the expression is a float literal and it is unsuffixed then
+ // add a suffix so the suggestion is valid and unambiguous
+ if let ExprKind::Lit(lit) = &arg.kind;
+ if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node;
+ then {
++ let op = format!("{sugg}{}", float_ty.name_str()).into();
+ match sugg {
+ sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op),
+ _ => sugg = sugg::Sugg::NonParen(op)
+ }
+ }
+ }
+
+ sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_par()).into());
+
+ // cast the result of `to_bits` if `to_ty` is signed
+ sugg = if let ty::Int(int_ty) = to_ty.kind() {
+ sugg.as_ty(int_ty.name_str().to_string())
+ } else {
+ sugg
+ };
+
+ diag.span_suggestion(e.span, "consider using", sugg, Applicability::Unspecified);
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("transmute from a `{}` to a `bool`", from_ty),
+use super::TRANSMUTE_INT_TO_BOOL;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg;
+use rustc_ast as ast;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use std::borrow::Cow;
+
+/// Checks for `transmute_int_to_bool` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_INT_TO_BOOL,
+ e.span,
++ &format!("transmute from a `{from_ty}` to a `bool`"),
+ |diag| {
+ let arg = sugg::Sugg::hir(cx, arg, "..");
+ let zero = sugg::Sugg::NonParen(Cow::from("0"));
+ diag.span_suggestion(
+ e.span,
+ "consider using",
+ sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string(),
+ Applicability::Unspecified,
+ );
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("transmute from a `{}` to a `char`", from_ty),
+use super::TRANSMUTE_INT_TO_CHAR;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg;
+use rustc_ast as ast;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+/// Checks for `transmute_int_to_char` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+ const_context: bool,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) if !const_context => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_INT_TO_CHAR,
+ e.span,
- format!("std::char::from_u32({}).unwrap()", arg),
++ &format!("transmute from a `{from_ty}` to a `char`"),
+ |diag| {
+ let arg = sugg::Sugg::hir(cx, arg, "..");
+ let arg = if let ty::Int(_) = from_ty.kind() {
+ arg.as_ty(ast::UintTy::U32.name_str())
+ } else {
+ arg
+ };
+ diag.span_suggestion(
+ e.span,
+ "consider using",
++ format!("std::char::from_u32({arg}).unwrap()"),
+ Applicability::Unspecified,
+ );
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
+use super::TRANSMUTE_INT_TO_FLOAT;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+/// Checks for `transmute_int_to_float` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+ const_context: bool,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_INT_TO_FLOAT,
+ e.span,
- format!("{}::from_bits({})", to_ty, arg),
++ &format!("transmute from a `{from_ty}` to a `{to_ty}`"),
+ |diag| {
+ let arg = sugg::Sugg::hir(cx, arg, "..");
+ let arg = if let ty::Int(int_ty) = from_ty.kind() {
+ arg.as_ty(format!(
+ "u{}",
+ int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string())
+ ))
+ } else {
+ arg
+ };
+ diag.span_suggestion(
+ e.span,
+ "consider using",
++ format!("{to_ty}::from_bits({arg})"),
+ Applicability::Unspecified,
+ );
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
+use super::TRANSMUTE_NUM_TO_BYTES;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty, UintTy};
+
+/// Checks for `transmute_int_to_float` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+ const_context: bool,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Int(_) | ty::Uint(_) | ty::Float(_), ty::Array(arr_ty, _)) => {
+ if !matches!(arr_ty.kind(), ty::Uint(UintTy::U8)) {
+ return false;
+ }
+ if matches!(from_ty.kind(), ty::Float(_)) && const_context {
+ // TODO: Remove when const_float_bits_conv is stabilized
+ // rust#72447
+ return false;
+ }
+
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_NUM_TO_BYTES,
+ e.span,
- format!("{}.to_ne_bytes()", arg),
++ &format!("transmute from a `{from_ty}` to a `{to_ty}`"),
+ |diag| {
+ let arg = sugg::Sugg::hir(cx, arg, "..");
+ diag.span_suggestion(
+ e.span,
+ "consider using `to_ne_bytes()`",
++ format!("{arg}.to_ne_bytes()"),
+ Applicability::Unspecified,
+ );
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!(
- "transmute from a pointer type (`{}`) to a reference type (`{}`)",
- from_ty, to_ty
- ),
+use super::TRANSMUTE_PTR_TO_REF;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{meets_msrv, msrvs, sugg};
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty, TypeVisitable};
+use rustc_semver::RustcVersion;
+
+/// Checks for `transmute_ptr_to_ref` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+ path: &'tcx Path<'_>,
+ msrv: Option<RustcVersion>,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_PTR_TO_REF,
+ e.span,
- format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), ty_snip)
++ &format!("transmute from a pointer type (`{from_ty}`) to a reference type (`{to_ty}`)"),
+ |diag| {
+ let arg = sugg::Sugg::hir(cx, arg, "..");
+ let (deref, cast) = if *mutbl == Mutability::Mut {
+ ("&mut *", "*mut")
+ } else {
+ ("&*", "*const")
+ };
+ let mut app = Applicability::MachineApplicable;
+
+ let sugg = if let Some(ty) = get_explicit_type(path) {
+ let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app);
+ if meets_msrv(msrv, msrvs::POINTER_CAST) {
- sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, ty_snip)))
- .to_string()
++ format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_par())
+ } else if from_ptr_ty.has_erased_regions() {
- sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, ty_snip))).to_string()
++ sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string()
+ } else {
- format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), to_ref_ty)
++ sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string()
+ }
+ } else if from_ptr_ty.ty == *to_ref_ty {
+ if from_ptr_ty.has_erased_regions() {
+ if meets_msrv(msrv, msrvs::POINTER_CAST) {
- sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, to_ref_ty)))
++ format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_par())
+ } else {
- sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, to_ref_ty))).to_string()
++ sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}")))
+ .to_string()
+ }
+ } else {
+ sugg::make_unop(deref, arg).to_string()
+ }
+ } else {
++ sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string()
+ };
+
+ diag.span_suggestion(e.span, "try", sugg, app);
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
+
+/// Gets the type `Bar` in `…::transmute<Foo, &Bar>`.
+fn get_explicit_type<'tcx>(path: &'tcx Path<'tcx>) -> Option<&'tcx hir::Ty<'tcx>> {
+ if let GenericArg::Type(ty) = path.segments.last()?.args?.args.get(1)?
+ && let TyKind::Rptr(_, ty) = &ty.kind
+ {
+ Some(ty.ty)
+ } else {
+ None
+ }
+}
--- /dev/null
- &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
+use super::{TRANSMUTE_BYTES_TO_STR, TRANSMUTE_PTR_TO_PTR};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, Mutability};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+/// Checks for `transmute_bytes_to_str` and `transmute_ptr_to_ptr` lints.
+/// Returns `true` if either one triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+ const_context: bool,
+) -> bool {
+ let mut triggered = false;
+
+ if let (ty::Ref(_, ty_from, from_mutbl), ty::Ref(_, ty_to, to_mutbl)) = (&from_ty.kind(), &to_ty.kind()) {
+ if_chain! {
+ if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind(), &ty_to.kind());
+ if let ty::Uint(ty::UintTy::U8) = slice_ty.kind();
+ if from_mutbl == to_mutbl;
+ then {
+ let postfix = if *from_mutbl == Mutability::Mut {
+ "_mut"
+ } else {
+ ""
+ };
+
+ let snippet = snippet(cx, arg.span, "..");
+
+ span_lint_and_sugg(
+ cx,
+ TRANSMUTE_BYTES_TO_STR,
+ e.span,
++ &format!("transmute from a `{from_ty}` to a `{to_ty}`"),
+ "consider using",
+ if const_context {
+ format!("std::str::from_utf8_unchecked{postfix}({snippet})")
+ } else {
+ format!("std::str::from_utf8{postfix}({snippet}).unwrap()")
+ },
+ Applicability::MaybeIncorrect,
+ );
+ triggered = true;
+ } else {
+ if (cx.tcx.erase_regions(from_ty) != cx.tcx.erase_regions(to_ty))
+ && !const_context {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_PTR_TO_PTR,
+ e.span,
+ "transmute from a reference to a reference",
+ |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
+ let ty_from_and_mut = ty::TypeAndMut {
+ ty: *ty_from,
+ mutbl: *from_mutbl
+ };
+ let ty_to_and_mut = ty::TypeAndMut { ty: *ty_to, mutbl: *to_mutbl };
+ let sugg_paren = arg
+ .as_ty(cx.tcx.mk_ptr(ty_from_and_mut))
+ .as_ty(cx.tcx.mk_ptr(ty_to_and_mut));
+ let sugg = if *to_mutbl == Mutability::Mut {
+ sugg_paren.mut_addr_deref()
+ } else {
+ sugg_paren.addr_deref()
+ };
+ diag.span_suggestion(
+ e.span,
+ "try",
+ sugg,
+ Applicability::Unspecified,
+ );
+ },
+ );
+
+ triggered = true;
+ }
+ }
+ }
+ }
+
+ triggered
+}
--- /dev/null
- &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
+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};
+use rustc_span::DUMMY_SP;
+
+#[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::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;
+ },
+
+ // 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,
- diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
++ &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if from_ty_orig.peel_refs() != from_ty.peel_refs() {
- &format!("transmute to `{}` which has an undefined layout", to_ty_orig),
++ diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
+ }
+ },
+ );
+ return true;
+ },
+ (_, ReducedTy::Other(_)) if reduced_tys.to_fat_ptr => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
- diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
++ &format!("transmute to `{to_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if to_ty_orig.peel_refs() != to_ty.peel_refs() {
- "transmute from `{}` to `{}`, both of which have an undefined layout",
- from_ty_orig, to_ty_orig
++ 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 => {
+ 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!(
- diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
++ "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 `{}` has an undefined layout", to_ty));
++ diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
+ }
+ if to_ty_orig.peel_refs() != to_ty {
- &format!("transmute from `{}` which has an undefined layout", from_ty_orig),
++ diag.note(&format!("the contained type `{to_ty}` has an undefined layout"));
+ }
+ }
+ },
+ );
+ return true;
+ },
+ (
+ ReducedTy::UnorderedFields(from_ty),
+ ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
+ ) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
- diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
++ &format!("transmute from `{from_ty_orig}` which has an undefined layout"),
+ |diag| {
+ if from_ty_orig.peel_refs() != from_ty {
- &format!("transmute into `{}` which has an undefined layout", to_ty_orig),
++ diag.note(&format!("the contained type `{from_ty}` has an undefined layout"));
+ }
+ },
+ );
+ return true;
+ },
+ (
+ ReducedTy::Other(_) | ReducedTy::OrderedFields(..) | ReducedTy::TypeErasure { raw_ptr_only: true },
+ ReducedTy::UnorderedFields(to_ty),
+ ) => {
+ span_lint_and_then(
+ cx,
+ TRANSMUTE_UNDEFINED_REPR,
+ e.span,
- diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
++ &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::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => {
+ break;
+ },
+ }
+ }
+
+ 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.
+fn reduce_refs<'tcx>(cx: &LateContext<'tcx>, mut from_ty: Ty<'tcx>, mut to_ty: Ty<'tcx>) -> ReducedTys<'tcx> {
+ let mut from_raw_ptr = false;
+ let mut to_raw_ptr = false;
+ let (from_fat_ptr, to_fat_ptr) = loop {
+ break match (from_ty.kind(), to_ty.kind()) {
+ (
+ &(ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. })),
+ &(ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. })),
+ ) => {
+ from_raw_ptr = matches!(*from_ty.kind(), ty::RawPtr(_));
+ from_ty = from_sub_ty;
+ to_raw_ptr = matches!(*to_ty.kind(), ty::RawPtr(_));
+ to_ty = to_sub_ty;
+ continue;
+ },
+ (&(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })), _)
+ if !unsized_ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env) =>
+ {
+ (true, false)
+ },
+ (_, &(ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })))
+ if !unsized_ty.is_sized(cx.tcx.at(DUMMY_SP), 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, 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 }
+ },
+ // 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
- &format!(
- "transmute from `{}` to `{}` which could be expressed as a pointer cast instead",
- from_ty, to_ty
- ),
+use super::utils::can_be_expressed_as_pointer_cast;
+use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+
+/// Checks for `transmutes_expressible_as_ptr_casts` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+) -> bool {
+ if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) {
+ span_lint_and_then(
+ cx,
+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ e.span,
++ &format!("transmute from `{from_ty}` to `{to_ty}` which could be expressed as a pointer cast instead"),
+ |diag| {
+ if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
+ let sugg = arg.as_ty(&to_ty.to_string()).to_string();
+ diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
+ }
+ },
+ );
+ true
+ } else {
+ false
+ }
+}
--- /dev/null
- use clippy_utils::is_path_diagnostic_item;
- use if_chain::if_chain;
- use rustc_ast::LitKind;
+use clippy_utils::consts::{constant_context, Constant};
+use clippy_utils::diagnostics::span_lint;
- if_chain! {
- if let ExprKind::Path(ref _qpath) = arg.kind;
- if let Some(Constant::RawPtr(x)) = const_eval_context.expr(arg);
- if x == 0;
- then {
- span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
- return true;
- }
++use clippy_utils::{is_integer_literal, is_path_diagnostic_item};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::symbol::sym;
+
+use super::TRANSMUTING_NULL;
+
+const LINT_MSG: &str = "transmuting a known null pointer into a reference";
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>, to_ty: Ty<'tcx>) -> bool {
+ if !to_ty.is_ref() {
+ return false;
+ }
+
+ // Catching transmute over constants that resolve to `null`.
+ let mut const_eval_context = constant_context(cx, cx.typeck_results());
- if_chain! {
- if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind;
- if let ExprKind::Lit(ref lit) = inner_expr.kind;
- if let LitKind::Int(0, _) = lit.node;
- then {
- span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
- return true;
- }
++ if let ExprKind::Path(ref _qpath) = arg.kind &&
++ let Some(Constant::RawPtr(x)) = const_eval_context.expr(arg) &&
++ x == 0
++ {
++ span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
++ return true;
+ }
+
+ // Catching:
+ // `std::mem::transmute(0 as *const i32)`
- if_chain! {
- if let ExprKind::Call(func1, []) = arg.kind;
- if is_path_diagnostic_item(cx, func1, sym::ptr_null);
- then {
- span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
- return true;
- }
++ if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind && is_integer_literal(inner_expr, 0) {
++ span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
++ return true;
+ }
+
+ // Catching:
+ // `std::mem::transmute(std::ptr::null::<i32>())`
++ if let ExprKind::Call(func1, []) = arg.kind &&
++ is_path_diagnostic_item(cx, func1, sym::ptr_null)
++ {
++ span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
++ return true;
+ }
+
+ // FIXME:
+ // Also catch transmutations of variables which are known nulls.
+ // To do this, MIR const propagation seems to be the better tool.
+ // Whenever MIR const prop routines are more developed, this will
+ // become available. As of this writing (25/03/19) it is not yet.
+ false
+}
--- /dev/null
- &format!(
- "transmute from `{}` to `{}` with mismatched layout is unsound",
- from_ty, to_ty
- ),
+use super::utils::is_layout_incompatible;
+use super::UNSOUND_COLLECTION_TRANSMUTE;
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+
+/// Checks for `unsound_collection_transmute` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => {
+ if from_adt.did() != to_adt.did() {
+ return false;
+ }
+ if !matches!(
+ cx.tcx.get_diagnostic_name(to_adt.did()),
+ Some(
+ sym::BTreeMap
+ | sym::BTreeSet
+ | sym::BinaryHeap
+ | sym::HashMap
+ | sym::HashSet
+ | sym::Vec
+ | sym::VecDeque
+ )
+ ) {
+ return false;
+ }
+ if from_substs
+ .types()
+ .zip(to_substs.types())
+ .any(|(from_ty, to_ty)| is_layout_incompatible(cx, from_ty, to_ty))
+ {
+ span_lint(
+ cx,
+ UNSOUND_COLLECTION_TRANSMUTE,
+ e.span,
++ &format!("transmute from `{from_ty}` to `{to_ty}` with mismatched layout is unsound"),
+ );
+ true
+ } else {
+ false
+ }
+ },
+ _ => false,
+ }
+}
--- /dev/null
- &format!("transmute from a type (`{}`) to itself", from_ty),
+use super::USELESS_TRANSMUTE;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::sugg;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty, TypeVisitable};
+
+/// Checks for `useless_transmute` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ from_ty: Ty<'tcx>,
+ to_ty: Ty<'tcx>,
+ arg: &'tcx Expr<'_>,
+) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ _ if from_ty == to_ty && !from_ty.has_erased_regions() => {
+ span_lint(
+ cx,
+ USELESS_TRANSMUTE,
+ e.span,
++ &format!("transmute from a type (`{from_ty}`) to itself"),
+ );
+ true
+ },
+ (ty::Ref(_, rty, rty_mutbl), ty::RawPtr(ptr_ty)) => {
+ // No way to give the correct suggestion here. Avoid linting for now.
+ if !rty.has_erased_regions() {
+ span_lint_and_then(
+ cx,
+ USELESS_TRANSMUTE,
+ e.span,
+ "transmute from a reference to a pointer",
+ |diag| {
+ if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
+ let rty_and_mut = ty::TypeAndMut {
+ ty: *rty,
+ mutbl: *rty_mutbl,
+ };
+
+ let sugg = if *ptr_ty == rty_and_mut {
+ arg.as_ty(to_ty)
+ } else {
+ arg.as_ty(cx.tcx.mk_ptr(rty_and_mut)).as_ty(to_ty)
+ };
+
+ diag.span_suggestion(e.span, "try", sugg, Applicability::Unspecified);
+ }
+ },
+ );
+ }
+ true
+ },
+ (ty::Int(_) | ty::Uint(_), ty::RawPtr(_)) => {
+ span_lint_and_then(
+ cx,
+ USELESS_TRANSMUTE,
+ e.span,
+ "transmute from an integer to a pointer",
+ |diag| {
+ if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
+ diag.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()), Applicability::Unspecified);
+ }
+ },
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- use rustc_hir_analysis::check::{cast::{self, CastCheckResult}, FnCtxt, Inherited};
+use rustc_hir::Expr;
++use rustc_hir_analysis::check::{
++ cast::{self, CastCheckResult},
++ FnCtxt, Inherited,
++};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{cast::CastKind, Ty};
+use rustc_span::DUMMY_SP;
+
+// 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 CastCheckResult::Deferred(check) = cast::check_cast(
+ &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,
+ ) {
+ 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
- &format!("transmute from a `{}` to a pointer", from_ty),
+use super::WRONG_TRANSMUTE;
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+/// Checks for `wrong_transmute` lint.
+/// Returns `true` if it's triggered, otherwise returns `false`.
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
+ match (&from_ty.kind(), &to_ty.kind()) {
+ (ty::Float(_) | ty::Char, ty::Ref(..) | ty::RawPtr(_)) => {
+ span_lint(
+ cx,
+ WRONG_TRANSMUTE,
+ e.span,
++ &format!("transmute from a `{from_ty}` to a pointer"),
+ );
+ true
+ },
+ _ => false,
+ }
+}
--- /dev/null
- format!("&{}({})", ltopt, &inner_snippet)
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, GenericArg, GenericBounds, GenericParamKind};
+use rustc_hir::{HirId, Lifetime, MutTy, Mutability, Node, QPath, TyKind};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::BORROWED_BOX;
+
+pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, mut_ty: &MutTy<'_>) -> bool {
+ match mut_ty.ty.kind {
+ TyKind::Path(ref qpath) => {
+ let hir_id = mut_ty.ty.hir_id;
+ let def = cx.qpath_res(qpath, hir_id);
+ if_chain! {
+ if let Some(def_id) = def.opt_def_id();
+ if Some(def_id) == cx.tcx.lang_items().owned_box();
+ if let QPath::Resolved(None, path) = *qpath;
+ if let [ref bx] = *path.segments;
+ if let Some(params) = bx.args;
+ if !params.parenthesized;
+ if let Some(inner) = params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ then {
+ if is_any_trait(cx, inner) {
+ // Ignore `Box<Any>` types; see issue #1884 for details.
+ return false;
+ }
+
+ let ltopt = if lt.name.is_anonymous() {
+ String::new()
+ } else {
+ format!("{} ", lt.name.ident().as_str())
+ };
+
+ if mut_ty.mutbl == Mutability::Mut {
+ // Ignore `&mut Box<T>` types; see issue #2907 for
+ // details.
+ return false;
+ }
+
+ // When trait objects or opaque types have lifetime or auto-trait bounds,
+ // we need to add parentheses to avoid a syntax error due to its ambiguity.
+ // Originally reported as the issue #3128.
+ let inner_snippet = snippet(cx, inner.span, "..");
+ let suggestion = match &inner.kind {
+ TyKind::TraitObject(bounds, lt_bound, _) if bounds.len() > 1 || !lt_bound.is_elided() => {
- format!("&{}({})", ltopt, &inner_snippet)
++ format!("&{ltopt}({})", &inner_snippet)
+ },
+ TyKind::Path(qpath)
+ if get_bounds_if_impl_trait(cx, qpath, inner.hir_id)
+ .map_or(false, |bounds| bounds.len() > 1) =>
+ {
- _ => format!("&{}{}", ltopt, &inner_snippet),
++ format!("&{ltopt}({})", &inner_snippet)
+ },
++ _ => format!("&{ltopt}{}", &inner_snippet),
+ };
+ span_lint_and_sugg(
+ cx,
+ BORROWED_BOX,
+ hir_ty.span,
+ "you seem to be trying to use `&Box<T>`. Consider using just `&T`",
+ "try",
+ suggestion,
+ // To make this `MachineApplicable`, at least one needs to check if it isn't a trait item
+ // because the trait impls of it will break otherwise;
+ // and there may be other cases that result in invalid code.
+ // For example, type coercion doesn't work nicely.
+ Applicability::Unspecified,
+ );
+ return true;
+ }
+ };
+ false
+ },
+ _ => false,
+ }
+}
+
+// Returns true if given type is `Any` trait.
+fn is_any_trait(cx: &LateContext<'_>, t: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::TraitObject(traits, ..) = t.kind;
+ if !traits.is_empty();
+ if let Some(trait_did) = traits[0].trait_ref.trait_def_id();
+ // Only Send/Sync can be used as additional traits, so it is enough to
+ // check only the first trait.
+ if cx.tcx.is_diagnostic_item(sym::Any, trait_did);
+ then {
+ return true;
+ }
+ }
+
+ false
+}
+
+fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option<GenericBounds<'tcx>> {
+ if_chain! {
+ if let Some(did) = cx.qpath_res(qpath, id).opt_def_id();
+ if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did);
+ if let GenericParamKind::Type { synthetic, .. } = generic_param.kind;
+ if synthetic;
+ if let Some(generics) = cx.tcx.hir().get_generics(id.owner.def_id);
+ if let Some(pred) = generics.bounds_for_param(did.expect_local()).next();
+ then {
+ Some(pred.bounds)
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- let box_content = format!("{outer}{generic}", outer = item_type);
+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::String
+ | sym::Vec
+ | sym::HashSet
+ | sym::VecDeque
+ | sym::LinkedList
+ | sym::BTreeMap
+ | sym::BTreeSet
+ | sym::BinaryHeap
+ )
+ })
+}
--- /dev/null
- 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)
+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.access_levels.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.access_levels.is_exported(item.def_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, _) => {
- TyKind::Rptr(ref lt, ref mut_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::TyAlias(..) => (),
+ }
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
+ let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(field.hir_id));
+
+ self.check_ty(
+ cx,
+ field.ty,
+ CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) {
+ let is_exported = cx.access_levels.is_exported(item.def_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
- format!("Rc<{}>", alternate),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::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_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::RC_BUFFER;
+
+pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
+ if cx.tcx.is_diagnostic_item(sym::Rc, def_id) {
+ if let Some(alternate) = match_buffer_type(cx, qpath) {
+ span_lint_and_sugg(
+ cx,
+ RC_BUFFER,
+ hir_ty.span,
+ "usage of `Rc<T>` when T is a buffer type",
+ "try",
- format!("Arc<{}>", alternate),
++ format!("Rc<{alternate}>"),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
+ let Some(id) = path_def_id(cx, ty) else { return false };
+ if !cx.tcx.is_diagnostic_item(sym::Vec, id) {
+ return false;
+ }
+ let qpath = match &ty.kind {
+ TyKind::Path(qpath) => qpath,
+ _ => return false,
+ };
+ let inner_span = match qpath_generic_tys(qpath).next() {
+ Some(ty) => ty.span,
+ None => return false,
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ RC_BUFFER,
+ hir_ty.span,
+ "usage of `Rc<T>` when T is a buffer type",
+ "try",
+ format!(
+ "Rc<[{}]>",
+ snippet_with_applicability(cx, inner_span, "..", &mut applicability)
+ ),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) {
+ if let Some(alternate) = match_buffer_type(cx, qpath) {
+ span_lint_and_sugg(
+ cx,
+ RC_BUFFER,
+ hir_ty.span,
+ "usage of `Arc<T>` when T is a buffer type",
+ "try",
++ format!("Arc<{alternate}>"),
+ Applicability::MachineApplicable,
+ );
+ } else if let Some(ty) = qpath_generic_tys(qpath).next() {
+ let Some(id) = path_def_id(cx, ty) else { return false };
+ if !cx.tcx.is_diagnostic_item(sym::Vec, id) {
+ return false;
+ }
+ let qpath = match &ty.kind {
+ TyKind::Path(qpath) => qpath,
+ _ => return false,
+ };
+ let inner_span = match qpath_generic_tys(qpath).next() {
+ Some(ty) => ty.span,
+ None => return false,
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ RC_BUFFER,
+ hir_ty.span,
+ "usage of `Arc<T>` when T is a buffer type",
+ "try",
+ format!(
+ "Arc<[{}]>",
+ snippet_with_applicability(cx, inner_span, "..", &mut applicability)
+ ),
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
+ }
+
+ false
+}
+
+fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> {
+ let ty = qpath_generic_tys(qpath).next()?;
+ let id = path_def_id(cx, ty)?;
+ let path = match cx.tcx.get_diagnostic_name(id)? {
+ sym::String => "str",
+ sym::OsString => "std::ffi::OsStr",
+ sym::PathBuf => "std::path::Path",
+ _ => return None,
+ };
+ Some(path)
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+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_span::symbol::sym;
- &format!("usage of `{}<{}>`", outer_sym, generic_snippet),
+
+use super::{utils, REDUNDANT_ALLOCATION};
+
+pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
+ 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 mut applicability = Applicability::MaybeIncorrect;
+ let generic_snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
- diag.span_suggestion(hir_ty.span, "try", format!("{}", generic_snippet), applicability);
++ &format!("usage of `{outer_sym}<{generic_snippet}>`"),
+ |diag| {
- "`{generic}` is already a pointer, `{outer}<{generic}>` allocates a pointer on the heap",
- outer = outer_sym,
- generic = generic_snippet
++ diag.span_suggestion(hir_ty.span, "try", format!("{generic_snippet}"), applicability);
+ diag.note(&format!(
- &format!("usage of `{}<{}<{}>>`", outer_sym, inner_sym, generic_snippet),
++ "`{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 inner_qpath = match &ty.kind {
+ TyKind::Path(inner_qpath) => inner_qpath,
+ _ => return false,
+ };
+ let inner_span = match qpath_generic_tys(inner_qpath).next() {
+ Some(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.
+ if !hir_ty_to_ty(cx.tcx, ty).is_sized(cx.tcx.at(ty.span), cx.param_env) {
+ return false;
+ }
+ ty.span
+ },
+ None => return false,
+ };
+ if inner_sym == outer_sym {
+ let mut applicability = Applicability::MaybeIncorrect;
+ let generic_snippet = snippet_with_applicability(cx, inner_span, "..", &mut applicability);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_ALLOCATION,
+ hir_ty.span,
- format!("{}<{}>", outer_sym, generic_snippet),
++ &format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"),
+ |diag| {
+ diag.span_suggestion(
+ hir_ty.span,
+ "try",
- "`{inner}<{generic}>` is already on the heap, `{outer}<{inner}<{generic}>>` makes an extra allocation",
- outer = outer_sym,
- inner = inner_sym,
- generic = generic_snippet
++ format!("{outer_sym}<{generic_snippet}>"),
+ applicability,
+ );
+ diag.note(&format!(
- &format!("usage of `{}<{}<{}>>`", outer_sym, inner_sym, generic_snippet),
++ "`{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,
- "`{inner}<{generic}>` is already on the heap, `{outer}<{inner}<{generic}>>` makes an extra allocation",
- outer = outer_sym,
- inner = inner_sym,
- generic = generic_snippet
++ &format!("usage of `{outer_sym}<{inner_sym}<{generic_snippet}>>`"),
+ |diag| {
+ diag.note(&format!(
- "consider using just `{outer}<{generic}>` or `{inner}<{generic}>`",
- outer = outer_sym,
- inner = inner_sym,
- generic = generic_snippet
++ "`{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
- use rustc_hir_analysis::hir_ty_to_ty;
+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.at(ty.span), 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
- use clippy_utils::{is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
+use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
- ExprKind::MethodCall(path, self_expr, [_], _) => {
++use clippy_utils::{is_integer_literal, is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
+use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
+ /// This is commonly caused by calling `set_len()` right after allocating or
+ /// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
+ ///
+ /// ### Why is this bad?
+ /// It creates a `Vec` with uninitialized data, which leads to
+ /// undefined behavior with most safe operations. Notably, uninitialized
+ /// `Vec<u8>` must not be used with generic `Read`.
+ ///
+ /// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
+ /// creates out-of-bound values that lead to heap memory corruption when used.
+ ///
+ /// ### Known Problems
+ /// This lint only checks directly adjacent statements.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ /// unsafe { vec.set_len(1000); }
+ /// reader.read(&mut vec); // undefined behavior!
+ /// ```
+ ///
+ /// ### How to fix?
+ /// 1. Use an initialized buffer:
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = vec![0; 1000];
+ /// reader.read(&mut vec);
+ /// ```
+ /// 2. Wrap the content in `MaybeUninit`:
+ /// ```rust,ignore
+ /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
+ /// vec.set_len(1000); // `MaybeUninit` can be uninitialized
+ /// ```
+ /// 3. If you are on 1.60.0 or later, `Vec::spare_capacity_mut()` is available:
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ /// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
+ /// // perform initialization with `remaining`
+ /// vec.set_len(...); // Safe to call `set_len()` on initialized part
+ /// ```
+ #[clippy::version = "1.58.0"]
+ pub UNINIT_VEC,
+ correctness,
+ "Vec with uninitialized data"
+}
+
+declare_lint_pass!(UninitVec => [UNINIT_VEC]);
+
+// FIXME: update to a visitor-based implementation.
+// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
+impl<'tcx> LateLintPass<'tcx> for UninitVec {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ if !in_external_macro(cx.tcx.sess, block.span) {
+ for w in block.stmts.windows(2) {
+ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
+ handle_uninit_vec_pair(cx, &w[0], expr);
+ }
+ }
+
+ if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
+ handle_uninit_vec_pair(cx, stmt, expr);
+ }
+ }
+ }
+}
+
+fn handle_uninit_vec_pair<'tcx>(
+ cx: &LateContext<'tcx>,
+ maybe_init_or_reserve: &'tcx Stmt<'tcx>,
+ maybe_set_len: &'tcx Expr<'tcx>,
+) {
+ if_chain! {
+ if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
+ if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
+ if vec.location.eq_expr(cx, set_len_self);
+ if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
+ if let ty::Adt(_, substs) = vec_ty.kind();
+ // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
+ if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id);
+ then {
+ if vec.has_capacity() {
+ // with_capacity / reserve -> set_len
+
+ // Check T of Vec<T>
+ if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) {
+ // FIXME: #7698, false positive of the internal lints
+ #[expect(clippy::collapsible_span_lint_calls)]
+ span_lint_and_then(
+ cx,
+ UNINIT_VEC,
+ vec![call_span, maybe_init_or_reserve.span],
+ "calling `set_len()` immediately after reserving a buffer creates uninitialized values",
+ |diag| {
+ diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
+ },
+ );
+ }
+ } else {
+ // new / default -> set_len
+ span_lint(
+ cx,
+ UNINIT_VEC,
+ vec![call_span, maybe_init_or_reserve.span],
+ "calling `set_len()` on empty `Vec` creates out-of-bound values",
+ );
+ }
+ }
+ }
+}
+
+/// The target `Vec` that is initialized or reserved
+#[derive(Clone, Copy)]
+struct TargetVec<'tcx> {
+ location: VecLocation<'tcx>,
+ /// `None` if `reserve()`
+ init_kind: Option<VecInitKind>,
+}
+
+impl TargetVec<'_> {
+ pub fn has_capacity(self) -> bool {
+ !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default))
+ }
+}
+
+#[derive(Clone, Copy)]
+enum VecLocation<'tcx> {
+ Local(HirId),
+ Expr(&'tcx Expr<'tcx>),
+}
+
+impl<'tcx> VecLocation<'tcx> {
+ pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ match self {
+ VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id),
+ VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
+ }
+ }
+}
+
+/// Finds the target location where the result of `Vec` initialization is stored
+/// or `self` expression for `Vec::reserve()`.
+fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if_chain! {
+ if let Some(init_expr) = local.init;
+ if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
+ if let Some(init_kind) = get_vec_init_kind(cx, init_expr);
+ then {
+ return Some(TargetVec {
+ location: VecLocation::Local(hir_id),
+ init_kind: Some(init_kind),
+ })
+ }
+ }
+ },
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ ExprKind::Assign(lhs, rhs, _span) => {
+ if let Some(init_kind) = get_vec_init_kind(cx, rhs) {
+ return Some(TargetVec {
+ location: VecLocation::Expr(lhs),
+ init_kind: Some(init_kind),
+ });
+ }
+ },
+ ExprKind::MethodCall(path, self_expr, [_], _) if is_reserve(cx, path, self_expr) => {
+ return Some(TargetVec {
+ location: VecLocation::Expr(self_expr),
+ init_kind: None,
+ });
+ },
+ _ => (),
+ },
+ StmtKind::Item(_) => (),
+ }
+ None
+}
+
+fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
+ && path.ident.name.as_str() == "reserve"
+}
+
+/// Returns self if the expression is `Vec::set_len()`
+fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> {
+ // peel unsafe blocks in `unsafe { vec.set_len() }`
+ let expr = peel_hir_expr_while(expr, |e| {
+ if let ExprKind::Block(block, _) = e.kind {
+ // Extract the first statement/expression
+ match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) {
+ (None, Some(expr)) => Some(expr),
+ (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ });
+ match expr.kind {
- if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
++ ExprKind::MethodCall(path, self_expr, [arg], _) => {
+ let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
++ if is_type_diagnostic_item(cx, self_type, sym::Vec)
++ && path.ident.name.as_str() == "set_len"
++ && !is_integer_literal(arg, 0)
++ {
+ Some((self_expr, expr.span))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
--- /dev/null
- the unit type which also implements {}",
- trait_name
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::{get_trait_def_id, paths};
+use if_chain::if_chain;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Closure, Expr, ExprKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_middle::ty::{GenericPredicates, PredicateKind, ProjectionPredicate, TraitPredicate};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{BytePos, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions that expect closures of type
+ /// Fn(...) -> Ord where the implemented closure returns the unit type.
+ /// The lint also suggests to remove the semi-colon at the end of the statement if present.
+ ///
+ /// ### Why is this bad?
+ /// Likely, returning the unit type is unintentional, and
+ /// could simply be caused by an extra semi-colon. Since () implements Ord
+ /// it doesn't cause a compilation error.
+ /// This is the same reasoning behind the unit_cmp lint.
+ ///
+ /// ### Known problems
+ /// If returning unit is intentional, then there is no
+ /// way of specifying this without triggering needless_return lint
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut twins = vec!((1, 1), (2, 2));
+ /// twins.sort_by_key(|x| { x.1; });
+ /// ```
+ #[clippy::version = "1.47.0"]
+ pub UNIT_RETURN_EXPECTING_ORD,
+ correctness,
+ "fn arguments of type Fn(...) -> Ord returning the unit type ()."
+}
+
+declare_lint_pass!(UnitReturnExpectingOrd => [UNIT_RETURN_EXPECTING_ORD]);
+
+fn get_trait_predicates_for_trait_id<'tcx>(
+ cx: &LateContext<'tcx>,
+ generics: GenericPredicates<'tcx>,
+ trait_id: Option<DefId>,
+) -> Vec<TraitPredicate<'tcx>> {
+ let mut preds = Vec::new();
+ for (pred, _) in generics.predicates {
+ if_chain! {
+ if let PredicateKind::Trait(poly_trait_pred) = pred.kind().skip_binder();
+ let trait_pred = cx.tcx.erase_late_bound_regions(pred.kind().rebind(poly_trait_pred));
+ if let Some(trait_def_id) = trait_id;
+ if trait_def_id == trait_pred.trait_ref.def_id;
+ then {
+ preds.push(trait_pred);
+ }
+ }
+ }
+ preds
+}
+
+fn get_projection_pred<'tcx>(
+ cx: &LateContext<'tcx>,
+ generics: GenericPredicates<'tcx>,
+ trait_pred: TraitPredicate<'tcx>,
+) -> Option<ProjectionPredicate<'tcx>> {
+ generics.predicates.iter().find_map(|(proj_pred, _)| {
+ if let ty::PredicateKind::Projection(pred) = proj_pred.kind().skip_binder() {
+ let projection_pred = cx.tcx.erase_late_bound_regions(proj_pred.kind().rebind(pred));
+ if projection_pred.projection_ty.substs == trait_pred.trait_ref.substs {
+ return Some(projection_pred);
+ }
+ }
+ None
+ })
+}
+
+fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<(usize, String)> {
+ let mut args_to_check = Vec::new();
+ if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+ let fn_sig = cx.tcx.fn_sig(def_id);
+ let generics = cx.tcx.predicates_of(def_id);
+ let fn_mut_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().fn_mut_trait());
+ let ord_preds = get_trait_predicates_for_trait_id(cx, generics, get_trait_def_id(cx, &paths::ORD));
+ let partial_ord_preds =
+ get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().partial_ord_trait());
+ // Trying to call erase_late_bound_regions on fn_sig.inputs() gives the following error
+ // The trait `rustc::ty::TypeFoldable<'_>` is not implemented for
+ // `&[rustc_middle::ty::Ty<'_>]`
+ let inputs_output = cx.tcx.erase_late_bound_regions(fn_sig.inputs_and_output());
+ inputs_output
+ .iter()
+ .rev()
+ .skip(1)
+ .rev()
+ .enumerate()
+ .for_each(|(i, inp)| {
+ for trait_pred in &fn_mut_preds {
+ if_chain! {
+ if trait_pred.self_ty() == inp;
+ if let Some(return_ty_pred) = get_projection_pred(cx, generics, *trait_pred);
+ then {
+ if ord_preds.iter().any(|ord| Some(ord.self_ty()) == return_ty_pred.term.ty()) {
+ args_to_check.push((i, "Ord".to_string()));
+ } else if partial_ord_preds.iter().any(|pord| {
+ pord.self_ty() == return_ty_pred.term.ty().unwrap()
+ }) {
+ args_to_check.push((i, "PartialOrd".to_string()));
+ }
+ }
+ }
+ }
+ });
+ }
+ args_to_check
+}
+
+fn check_arg<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Option<(Span, Option<Span>)> {
+ if_chain! {
+ if let ExprKind::Closure(&Closure { body, fn_decl_span, .. }) = arg.kind;
+ if let ty::Closure(_def_id, substs) = &cx.typeck_results().node_type(arg.hir_id).kind();
+ let ret_ty = substs.as_closure().sig().output();
+ let ty = cx.tcx.erase_late_bound_regions(ret_ty);
+ if ty.is_unit();
+ then {
+ let body = cx.tcx.hir().body(body);
+ if_chain! {
+ if let ExprKind::Block(block, _) = body.value.kind;
+ if block.expr.is_none();
+ if let Some(stmt) = block.stmts.last();
+ if let StmtKind::Semi(_) = stmt.kind;
+ then {
+ let data = stmt.span.data();
+ // Make a span out of the semicolon for the help message
+ Some((fn_decl_span, Some(data.with_lo(data.hi-BytePos(1)))))
+ } else {
+ Some((fn_decl_span, None))
+ }
+ }
+ } else {
+ None
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnitReturnExpectingOrd {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if let ExprKind::MethodCall(_, receiver, args, _) = expr.kind {
+ let arg_indices = get_args_to_check(cx, expr);
+ let args = std::iter::once(receiver).chain(args.iter()).collect::<Vec<_>>();
+ for (i, trait_name) in arg_indices {
+ if i < args.len() {
+ match check_arg(cx, args[i]) {
+ Some((span, None)) => {
+ span_lint(
+ cx,
+ UNIT_RETURN_EXPECTING_ORD,
+ span,
+ &format!(
+ "this closure returns \
- the unit type which also implements {}",
- trait_name
++ the unit type which also implements {trait_name}"
+ ),
+ );
+ },
+ Some((span, Some(last_semi))) => {
+ span_lint_and_help(
+ cx,
+ UNIT_RETURN_EXPECTING_ORD,
+ span,
+ &format!(
+ "this closure returns \
++ the unit type which also implements {trait_name}"
+ ),
+ Some(last_semi),
+ "probably caused by this trailing semicolon",
+ );
+ },
+ None => {},
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- &format!("passing {}unit value{} to a function", singular, plural),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::is_from_proc_macro;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, Block, Expr, ExprKind, MatchSource, Node, StmtKind};
+use rustc_lint::LateContext;
+
+use super::{utils, UNIT_ARG};
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ // apparently stuff in the desugaring of `?` can trigger this
+ // so check for that here
+ // only the calls to `Try::from_error` is marked as desugared,
+ // so we need to check both the current Expr and its parent.
+ if is_questionmark_desugar_marked_call(expr) {
+ return;
+ }
+ let map = &cx.tcx.hir();
+ let opt_parent_node = map.find(map.get_parent_node(expr.hir_id));
+ if_chain! {
+ if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node;
+ if is_questionmark_desugar_marked_call(parent_expr);
+ then {
+ return;
+ }
+ }
+
+ let args: Vec<_> = match expr.kind {
+ ExprKind::Call(_, args) => args.iter().collect(),
+ ExprKind::MethodCall(_, receiver, args, _) => std::iter::once(receiver).chain(args.iter()).collect(),
+ _ => return,
+ };
+
+ let args_to_recover = args
+ .into_iter()
+ .filter(|arg| {
+ if cx.typeck_results().expr_ty(arg).is_unit() && !utils::is_unit_literal(arg) {
+ !matches!(
+ &arg.kind,
+ ExprKind::Match(.., MatchSource::TryDesugar) | ExprKind::Path(..)
+ )
+ } else {
+ false
+ }
+ })
+ .collect::<Vec<_>>();
+ if !args_to_recover.is_empty() && !is_from_proc_macro(cx, expr) {
+ lint_unit_args(cx, expr, args_to_recover.as_slice());
+ }
+}
+
+fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool {
+ use rustc_span::hygiene::DesugaringKind;
+ if let ExprKind::Call(callee, _) = expr.kind {
+ callee.span.is_desugaring(DesugaringKind::QuestionMark)
+ } else {
+ false
+ }
+}
+
+fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) {
+ let mut applicability = Applicability::MachineApplicable;
+ let (singular, plural) = if args_to_recover.len() > 1 {
+ ("", "s")
+ } else {
+ ("a ", "")
+ };
+ span_lint_and_then(
+ cx,
+ UNIT_ARG,
+ expr.span,
- &format!("use {}unit literal{} instead", singular, plural),
++ &format!("passing {singular}unit value{plural} to a function"),
+ |db| {
+ let mut or = "";
+ args_to_recover
+ .iter()
+ .filter_map(|arg| {
+ if_chain! {
+ if let ExprKind::Block(block, _) = arg.kind;
+ if block.expr.is_none();
+ if let Some(last_stmt) = block.stmts.iter().last();
+ if let StmtKind::Semi(last_expr) = last_stmt.kind;
+ if let Some(snip) = snippet_opt(cx, last_expr.span);
+ then {
+ Some((
+ last_stmt.span,
+ snip,
+ ))
+ }
+ else {
+ None
+ }
+ }
+ })
+ .for_each(|(span, sugg)| {
+ db.span_suggestion(
+ span,
+ "remove the semicolon from the last statement in the block",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ or = "or ";
+ applicability = Applicability::MaybeIncorrect;
+ });
+
+ let arg_snippets: Vec<String> = args_to_recover
+ .iter()
+ .filter_map(|arg| snippet_opt(cx, arg.span))
+ .collect();
+ let arg_snippets_without_empty_blocks: Vec<String> = args_to_recover
+ .iter()
+ .filter(|arg| !is_empty_block(arg))
+ .filter_map(|arg| snippet_opt(cx, arg.span))
+ .collect();
+
+ if let Some(call_snippet) = snippet_opt(cx, expr.span) {
+ let sugg = fmt_stmts_and_call(
+ cx,
+ expr,
+ &call_snippet,
+ &arg_snippets,
+ &arg_snippets_without_empty_blocks,
+ );
+
+ if arg_snippets_without_empty_blocks.is_empty() {
+ db.multipart_suggestion(
- "{}move the expression{} in front of the call and replace {} with the unit literal `()`",
- or, empty_or_s, it_or_them
++ &format!("use {singular}unit literal{plural} instead"),
+ args_to_recover
+ .iter()
+ .map(|arg| (arg.span, "()".to_string()))
+ .collect::<Vec<_>>(),
+ applicability,
+ );
+ } else {
+ let plural = arg_snippets_without_empty_blocks.len() > 1;
+ let empty_or_s = if plural { "s" } else { "" };
+ let it_or_them = if plural { "them" } else { "it" };
+ db.span_suggestion(
+ expr.span,
+ &format!(
++ "{or}move the expression{empty_or_s} in front of the call and replace {it_or_them} with the unit literal `()`"
+ ),
+ sugg,
+ applicability,
+ );
+ }
+ }
+ },
+ );
+}
+
+fn is_empty_block(expr: &Expr<'_>) -> bool {
+ matches!(
+ expr.kind,
+ ExprKind::Block(
+ Block {
+ stmts: &[],
+ expr: None,
+ ..
+ },
+ _,
+ )
+ )
+}
+
+fn fmt_stmts_and_call(
+ cx: &LateContext<'_>,
+ call_expr: &Expr<'_>,
+ call_snippet: &str,
+ args_snippets: &[impl AsRef<str>],
+ non_empty_block_args_snippets: &[impl AsRef<str>],
+) -> String {
+ let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0);
+ let call_snippet_with_replacements = args_snippets
+ .iter()
+ .fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1));
+
+ let mut stmts_and_call = non_empty_block_args_snippets
+ .iter()
+ .map(|it| it.as_ref().to_owned())
+ .collect::<Vec<_>>();
+ stmts_and_call.push(call_snippet_with_replacements);
+ stmts_and_call = stmts_and_call
+ .into_iter()
+ .map(|v| reindent_multiline(v.into(), true, Some(call_expr_indent)).into_owned())
+ .collect();
+
+ let mut stmts_and_call_snippet = stmts_and_call.join(&format!("{}{}", ";\n", " ".repeat(call_expr_indent)));
+ // expr is not in a block statement or result expression position, wrap in a block
+ let parent_node = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(call_expr.hir_id));
+ if !matches!(parent_node, Some(Node::Block(_))) && !matches!(parent_node, Some(Node::Stmt(_))) {
+ let block_indent = call_expr_indent + 4;
+ stmts_and_call_snippet =
+ reindent_multiline(stmts_and_call_snippet.into(), true, Some(block_indent)).into_owned();
+ stmts_and_call_snippet = format!(
+ "{{\n{}{}\n{}}}",
+ " ".repeat(block_indent),
+ &stmts_and_call_snippet,
+ " ".repeat(call_expr_indent)
+ );
+ }
+ stmts_and_call_snippet
+}
--- /dev/null
- &format!("`{}` of unit values detected. This will always {}", macro_name, result),
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::LateContext;
+
+use super::UNIT_CMP;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if expr.span.from_expansion() {
+ if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
+ let macro_name = cx.tcx.item_name(macro_call.def_id);
+ let result = match macro_name.as_str() {
+ "assert_eq" | "debug_assert_eq" => "succeed",
+ "assert_ne" | "debug_assert_ne" => "fail",
+ _ => return,
+ };
+ let Some ((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
+ if !cx.typeck_results().expr_ty(left).is_unit() {
+ return;
+ }
+ span_lint(
+ cx,
+ UNIT_CMP,
+ macro_call.span,
- "{}-comparison of unit values detected. This will always be {}",
- op.as_str(),
- result
++ &format!("`{macro_name}` of unit values detected. This will always {result}"),
+ );
+ }
+ return;
+ }
+
+ if let ExprKind::Binary(ref cmp, left, _) = expr.kind {
+ let op = cmp.node;
+ if op.is_comparison() && cx.typeck_results().expr_ty(left).is_unit() {
+ let result = match op {
+ BinOpKind::Eq | BinOpKind::Le | BinOpKind::Ge => "true",
+ _ => "false",
+ };
+ span_lint(
+ cx,
+ UNIT_CMP,
+ expr.span,
+ &format!(
++ "{}-comparison of unit values detected. This will always be {result}",
++ op.as_str()
+ ),
+ );
+ }
+ }
+}
--- /dev/null
- use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
+use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
- if match_def_path(cx, fun_def_id, &paths::FROM_FROM);
++use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Detects cases of owned empty strings being passed as an argument to a function expecting `&str`
+ ///
+ /// ### Why is this bad?
+ ///
+ /// This results in longer and less readable code
+ ///
+ /// ### Example
+ /// ```rust
+ /// vec!["1", "2", "3"].join(&String::new());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// vec!["1", "2", "3"].join("");
+ /// ```
+ #[clippy::version = "1.62.0"]
+ pub UNNECESSARY_OWNED_EMPTY_STRINGS,
+ style,
+ "detects cases of references to owned empty strings being passed as an argument to a function expecting `&str`"
+}
+declare_lint_pass!(UnnecessaryOwnedEmptyStrings => [UNNECESSARY_OWNED_EMPTY_STRINGS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner_expr) = expr.kind;
+ if let ExprKind::Call(fun, args) = inner_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 let ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
+ if inner_str.is_str();
+ then {
+ if match_def_path(cx, fun_def_id, &paths::STRING_NEW) {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_OWNED_EMPTY_STRINGS,
+ expr.span,
+ "usage of `&String::new()` for a function expecting a `&str` argument",
+ "try",
+ "\"\"".to_owned(),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ if_chain! {
++ if cx.tcx.lang_items().require(LangItem::FromFrom).ok() == Some(fun_def_id);
+ if let [.., last_arg] = args;
+ if let ExprKind::Lit(spanned) = &last_arg.kind;
+ if let LitKind::Str(symbol, _) = spanned.node;
+ if symbol.is_empty();
+ let inner_expr_type = cx.typeck_results().expr_ty(inner_expr);
+ if is_type_diagnostic_item(cx, inner_expr_type, sym::String);
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_OWNED_EMPTY_STRINGS,
+ expr.span,
+ "usage of `&String::from(\"\")` for a function expecting a `&str` argument",
+ "try",
+ "\"\"".to_owned(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- if let UseTreeKind::Simple(Some(alias), ..) = self_tree.kind { format!(" as {}", alias) } else { String::new() },
+use clippy_utils::diagnostics::span_lint_and_then;
+use if_chain::if_chain;
+use rustc_ast::{Item, ItemKind, UseTreeKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for imports ending in `::{self}`.
+ ///
+ /// ### Why is this bad?
+ /// In most cases, this can be written much more cleanly by omitting `::{self}`.
+ ///
+ /// ### Known problems
+ /// Removing `::{self}` will cause any non-module items at the same path to also be imported.
+ /// This might cause a naming conflict (https://github.com/rust-lang/rustfmt/issues/3568). This lint makes no attempt
+ /// to detect this scenario and that is why it is a restriction lint.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::io::{self};
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::io;
+ /// ```
+ #[clippy::version = "1.53.0"]
+ pub UNNECESSARY_SELF_IMPORTS,
+ restriction,
+ "imports ending in `::{self}`, which can be omitted"
+}
+
+declare_lint_pass!(UnnecessarySelfImports => [UNNECESSARY_SELF_IMPORTS]);
+
+impl EarlyLintPass for UnnecessarySelfImports {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if_chain! {
+ if let ItemKind::Use(use_tree) = &item.kind;
+ if let UseTreeKind::Nested(nodes) = &use_tree.kind;
+ if let [(self_tree, _)] = &**nodes;
+ if let [self_seg] = &*self_tree.prefix.segments;
+ if self_seg.ident.name == kw::SelfLower;
+ if let Some(last_segment) = use_tree.prefix.segments.last();
+
+ then {
+ span_lint_and_then(
+ cx,
+ UNNECESSARY_SELF_IMPORTS,
+ item.span,
+ "import ending with `::{self}`",
+ |diag| {
+ diag.span_suggestion(
+ last_segment.span().with_hi(item.span.hi()),
+ "consider omitting `::{self}`",
+ format!(
+ "{}{};",
+ last_segment.ident,
++ if let UseTreeKind::Simple(Some(alias), ..) = self_tree.kind { format!(" as {alias}") } else { String::new() },
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ diag.note("this will slightly change semantics; any non-module items at the same path will also be imported");
+ },
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{contains_return, is_lang_ctor, return_ty, visitors::find_all_ret_expressions};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
- // Check if OPTION_SOME or RESULT_OK, depending on return type.
- if let ExprKind::Path(qpath) = &func.kind;
- if is_lang_ctor(cx, qpath, lang_item);
++use clippy_utils::{contains_return, is_res_lang_ctor, path_res, return_ty, visitors::find_all_ret_expressions};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::LangItem::{OptionSome, ResultOk};
+use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for private functions that only return `Ok` or `Some`.
+ ///
+ /// ### Why is this bad?
+ /// It is not meaningful to wrap values when no `None` or `Err` is returned.
+ ///
+ /// ### Known problems
+ /// There can be false positives if the function signature is designed to
+ /// fit some external requirement.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn get_cool_number(a: bool, b: bool) -> Option<i32> {
+ /// if a && b {
+ /// return Some(50);
+ /// }
+ /// if a {
+ /// Some(0)
+ /// } else {
+ /// Some(10)
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn get_cool_number(a: bool, b: bool) -> i32 {
+ /// if a && b {
+ /// return 50;
+ /// }
+ /// if a {
+ /// 0
+ /// } else {
+ /// 10
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub UNNECESSARY_WRAPS,
+ pedantic,
+ "functions that only return `Ok` or `Some`"
+}
+
+pub struct UnnecessaryWraps {
+ avoid_breaking_exported_api: bool,
+}
+
+impl_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]);
+
+impl UnnecessaryWraps {
+ pub fn new(avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: FnKind<'tcx>,
+ fn_decl: &FnDecl<'tcx>,
+ body: &Body<'tcx>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ // Abort if public function/method or closure.
+ match fn_kind {
+ FnKind::ItemFn(..) | FnKind::Method(..) => {
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+ if self.avoid_breaking_exported_api && cx.access_levels.is_exported(def_id) {
+ return;
+ }
+ },
+ FnKind::Closure => return,
+ }
+
+ // Abort if the method is implementing a trait or of it a trait method.
+ 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;
+ }
+ }
+
+ // Get the wrapper and inner types, if can't, abort.
+ let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) {
+ ("Option", OptionSome, subst.type_at(0))
+ } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) {
+ ("Result", ResultOk, subst.type_at(0))
+ } else {
+ return;
+ }
+ } else {
+ return;
+ };
+
+ // Check if all return expression respect the following condition and collect them.
+ let mut suggs = Vec::new();
+ let can_sugg = find_all_ret_expressions(cx, body.value, |ret_expr| {
+ if_chain! {
+ if !ret_expr.span.from_expansion();
+ // Check if a function call.
+ if let ExprKind::Call(func, [arg]) = ret_expr.kind;
- format!(
- "this function's return value is unnecessarily wrapped by `{}`",
- return_type_label
- ),
- format!("remove `{}` from the return type...", return_type_label),
++ if is_res_lang_ctor(cx, path_res(cx, func), lang_item);
+ // Make sure the function argument does not contain a return expression.
+ if !contains_return(arg);
+ then {
+ suggs.push(
+ (
+ ret_expr.span,
+ if inner_type.is_unit() {
+ String::new()
+ } else {
+ snippet(cx, arg.span.source_callsite(), "..").to_string()
+ }
+ )
+ );
+ true
+ } else {
+ false
+ }
+ }
+ });
+
+ if can_sugg && !suggs.is_empty() {
+ let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
+ (
+ "this function's return value is unnecessary".to_string(),
+ "remove the return type...".to_string(),
+ snippet(cx, fn_decl.output.span(), "..").to_string(),
+ "...and then remove returned values",
+ )
+ } else {
+ (
++ format!("this function's return value is unnecessarily wrapped by `{return_type_label}`"),
++ format!("remove `{return_type_label}` from the return type..."),
+ inner_type.to_string(),
+ "...and then change returning expressions",
+ )
+ };
+
+ span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
+ diag.span_suggestion(
+ fn_decl.output.span(),
+ return_type_sugg_msg.as_str(),
+ return_type_sugg,
+ Applicability::MaybeIncorrect,
+ );
+ diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);
+ });
+ }
+ }
+}
--- /dev/null
- &format!(
- "removed `unsafe` from the name of `{}` in use as `{}`",
- old_str, new_str
- ),
+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 &(ref 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
- use clippy_utils::{is_try, match_trait_method, paths};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
- match_trait_method(cx, call, &paths::IO_READ)
++use clippy_utils::{is_trait_method, is_try, match_trait_method, paths};
+use rustc_hir as hir;
+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 unused written/read amount.
+ ///
+ /// ### Why is this bad?
+ /// `io::Write::write(_vectored)` and
+ /// `io::Read::read(_vectored)` are not guaranteed to
+ /// process the entire buffer. They return how many bytes were processed, which
+ /// might be smaller
+ /// than a given buffer's length. If you don't need to deal with
+ /// partial-write/read, use
+ /// `write_all`/`read_exact` instead.
+ ///
+ /// When working with asynchronous code (either with the `futures`
+ /// crate or with `tokio`), a similar issue exists for
+ /// `AsyncWriteExt::write()` and `AsyncReadExt::read()` : these
+ /// functions are also not guaranteed to process the entire
+ /// buffer. Your code should either handle partial-writes/reads, or
+ /// call the `write_all`/`read_exact` methods on those traits instead.
+ ///
+ /// ### Known problems
+ /// Detects only common patterns.
+ ///
+ /// ### Examples
+ /// ```rust,ignore
+ /// use std::io;
+ /// fn foo<W: io::Write>(w: &mut W) -> io::Result<()> {
+ /// // must be `w.write_all(b"foo")?;`
+ /// w.write(b"foo")?;
+ /// Ok(())
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNUSED_IO_AMOUNT,
+ correctness,
+ "unused written/read amount"
+}
+
+declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+ let expr = match s.kind {
+ hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
+ _ => return,
+ };
+
+ match expr.kind {
+ hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
+ if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
+ if matches!(
+ func.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
+ ) {
+ check_map_error(cx, arg_0, expr);
+ }
+ } else {
+ check_map_error(cx, res, expr);
+ }
+ },
+ hir::ExprKind::MethodCall(path, arg_0, ..) => match path.ident.as_str() {
+ "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => {
+ check_map_error(cx, arg_0, expr);
+ },
+ _ => (),
+ },
+ _ => (),
+ }
+ }
+}
+
+/// If `expr` is an (e).await, return the inner expression "e" that's being
+/// waited on. Otherwise return None.
+fn try_remove_await<'a>(expr: &'a hir::Expr<'a>) -> Option<&hir::Expr<'a>> {
+ if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
+ if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
+ if matches!(
+ func.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
+ ) {
+ return Some(arg_0);
+ }
+ }
+ }
+
+ None
+}
+
+fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
+ let mut call = call;
+ while let hir::ExprKind::MethodCall(path, receiver, ..) = call.kind {
+ if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
+ call = receiver;
+ } else {
+ break;
+ }
+ }
+
+ if let Some(call) = try_remove_await(call) {
+ check_method_call(cx, call, expr, true);
+ } else {
+ check_method_call(cx, call, expr, false);
+ }
+}
+
+fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>, is_await: bool) {
+ if let hir::ExprKind::MethodCall(path, ..) = call.kind {
+ let symbol = path.ident.as_str();
+ let read_trait = if is_await {
+ match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
+ || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT)
+ } else {
- match_trait_method(cx, call, &paths::IO_WRITE)
++ is_trait_method(cx, call, sym::IoRead)
+ };
+ let write_trait = if is_await {
+ match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT)
+ || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
+ } else {
++ is_trait_method(cx, call, sym::IoWrite)
+ };
+
+ match (read_trait, write_trait, symbol, is_await) {
+ (true, _, "read", false) => span_lint_and_help(
+ cx,
+ UNUSED_IO_AMOUNT,
+ expr.span,
+ "read amount is not handled",
+ None,
+ "use `Read::read_exact` instead, or handle partial reads",
+ ),
+ (true, _, "read", true) => span_lint_and_help(
+ cx,
+ UNUSED_IO_AMOUNT,
+ expr.span,
+ "read amount is not handled",
+ None,
+ "use `AsyncReadExt::read_exact` instead, or handle partial reads",
+ ),
+ (true, _, "read_vectored", _) => {
+ span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled");
+ },
+ (_, true, "write", false) => span_lint_and_help(
+ cx,
+ UNUSED_IO_AMOUNT,
+ expr.span,
+ "written amount is not handled",
+ None,
+ "use `Write::write_all` instead, or handle partial writes",
+ ),
+ (_, true, "write", true) => span_lint_and_help(
+ cx,
+ UNUSED_IO_AMOUNT,
+ expr.span,
+ "written amount is not handled",
+ None,
+ "use `AsyncWriteExt::write_all` instead, or handle partial writes",
+ ),
+ (_, true, "write_vectored", _) => {
+ span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled");
+ },
+ _ => (),
+ }
+ }
+}
--- /dev/null
- &format!("used the `{}` method with a whole number float", method_name),
- &format!("remove the `{}` method call", method_name),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast::ast::{Expr, ExprKind, LitFloatType, LitKind};
+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)> {
+ if let ExprKind::MethodCall(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(spanned) = &receiver.kind
+ && let LitKind::Float(symbol, ty) = spanned.kind {
+ let f = symbol.as_str().parse::<f64>().unwrap();
+ let f_str = symbol.to_string() + if let LitFloatType::Suffixed(ty) = ty {
+ ty.name_str()
+ } else {
+ ""
+ };
+ 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
- "called `{}` on `{}` after checking its variant with `{}`",
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::higher;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{path_to_local, usage::is_potentially_mutated};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
+use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::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 calls of `unwrap[_err]()` that cannot fail.
+ ///
+ /// ### Why is this bad?
+ /// Using `if let` or `match` is more idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let option = Some(0);
+ /// # fn do_something_with(_x: usize) {}
+ /// if option.is_some() {
+ /// do_something_with(option.unwrap())
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # let option = Some(0);
+ /// # fn do_something_with(_x: usize) {}
+ /// if let Some(value) = option {
+ /// do_something_with(value)
+ /// }
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_UNWRAP,
+ complexity,
+ "checks for calls of `unwrap[_err]()` that cannot fail"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls of `unwrap[_err]()` that will always fail.
+ ///
+ /// ### Why is this bad?
+ /// If panicking is desired, an explicit `panic!()` should be used.
+ ///
+ /// ### Known problems
+ /// This lint only checks `if` conditions not assignments.
+ /// So something like `let x: Option<()> = None; x.unwrap();` will not be recognized.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let option = Some(0);
+ /// # fn do_something_with(_x: usize) {}
+ /// if option.is_none() {
+ /// do_something_with(option.unwrap())
+ /// }
+ /// ```
+ ///
+ /// This code will always panic. The if condition should probably be inverted.
+ #[clippy::version = "pre 1.29.0"]
+ pub PANICKING_UNWRAP,
+ correctness,
+ "checks for calls of `unwrap[_err]()` that will always fail"
+}
+
+/// Visitor that keeps track of which variables are unwrappable.
+struct UnwrappableVariablesVisitor<'a, 'tcx> {
+ unwrappables: Vec<UnwrapInfo<'tcx>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+/// What kind of unwrappable this is.
+#[derive(Copy, Clone, Debug)]
+enum UnwrappableKind {
+ Option,
+ Result,
+}
+
+impl UnwrappableKind {
+ fn success_variant_pattern(self) -> &'static str {
+ match self {
+ UnwrappableKind::Option => "Some(..)",
+ UnwrappableKind::Result => "Ok(..)",
+ }
+ }
+
+ fn error_variant_pattern(self) -> &'static str {
+ match self {
+ UnwrappableKind::Option => "None",
+ UnwrappableKind::Result => "Err(..)",
+ }
+ }
+}
+
+/// Contains information about whether a variable can be unwrapped.
+#[derive(Copy, Clone, Debug)]
+struct UnwrapInfo<'tcx> {
+ /// The variable that is checked
+ local_id: HirId,
+ /// The if itself
+ if_expr: &'tcx Expr<'tcx>,
+ /// The check, like `x.is_ok()`
+ check: &'tcx Expr<'tcx>,
+ /// The check's name, like `is_ok`
+ check_name: &'tcx PathSegment<'tcx>,
+ /// The branch where the check takes place, like `if x.is_ok() { .. }`
+ branch: &'tcx Expr<'tcx>,
+ /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
+ safe_to_unwrap: bool,
+ /// What kind of unwrappable this is.
+ kind: UnwrappableKind,
+ /// If the check is the entire condition (`if x.is_ok()`) or only a part of it (`foo() &&
+ /// x.is_ok()`)
+ is_entire_condition: bool,
+}
+
+/// Collects the information about unwrappable variables from an if condition
+/// The `invert` argument tells us whether the condition is negated.
+fn collect_unwrap_info<'tcx>(
+ cx: &LateContext<'tcx>,
+ if_expr: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+ branch: &'tcx Expr<'_>,
+ invert: bool,
+ is_entire_condition: bool,
+) -> Vec<UnwrapInfo<'tcx>> {
+ fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
+ is_type_diagnostic_item(cx, ty, sym::Option) && ["is_some", "is_none"].contains(&method_name)
+ }
+
+ fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
+ is_type_diagnostic_item(cx, ty, sym::Result) && ["is_ok", "is_err"].contains(&method_name)
+ }
+
+ if let ExprKind::Binary(op, left, right) = &expr.kind {
+ match (invert, op.node) {
+ (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
+ let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
+ unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false));
+ return unwrap_info;
+ },
+ _ => (),
+ }
+ } else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
+ return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
+ } else {
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, receiver, args, _) = &expr.kind;
+ if let Some(local_id) = path_to_local(receiver);
+ let ty = cx.typeck_results().expr_ty(receiver);
+ let name = method_name.ident.as_str();
+ if is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name);
+ then {
+ assert!(args.is_empty());
+ let unwrappable = match name {
+ "is_some" | "is_ok" => true,
+ "is_err" | "is_none" => false,
+ _ => unreachable!(),
+ };
+ let safe_to_unwrap = unwrappable != invert;
+ let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
+ UnwrappableKind::Option
+ } else {
+ UnwrappableKind::Result
+ };
+
+ return vec![
+ UnwrapInfo {
+ local_id,
+ if_expr,
+ check: expr,
+ check_name: method_name,
+ branch,
+ safe_to_unwrap,
+ kind,
+ is_entire_condition,
+ }
+ ]
+ }
+ }
+ }
+ Vec::new()
+}
+
+impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
+ fn visit_branch(
+ &mut self,
+ if_expr: &'tcx Expr<'_>,
+ cond: &'tcx Expr<'_>,
+ branch: &'tcx Expr<'_>,
+ else_branch: bool,
+ ) {
+ let prev_len = self.unwrappables.len();
+ for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
+ if is_potentially_mutated(unwrap_info.local_id, cond, self.cx)
+ || is_potentially_mutated(unwrap_info.local_id, branch, self.cx)
+ {
+ // if the variable is mutated, we don't know whether it can be unwrapped:
+ continue;
+ }
+ self.unwrappables.push(unwrap_info);
+ }
+ walk_expr(self, branch);
+ self.unwrappables.truncate(prev_len);
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // Shouldn't lint when `expr` is in macro.
+ if in_external_macro(self.cx.tcx.sess, expr.span) {
+ return;
+ }
+ if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) {
+ walk_expr(self, cond);
+ self.visit_branch(expr, cond, then, false);
+ if let Some(else_inner) = r#else {
+ self.visit_branch(expr, cond, else_inner, true);
+ }
+ } else {
+ // find `unwrap[_err]()` calls:
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind;
+ if let Some(id) = path_to_local(self_arg);
+ if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name);
+ let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name);
+ if let Some(unwrappable) = self.unwrappables.iter()
+ .find(|u| u.local_id == id);
+ // Span contexts should not differ with the conditional branch
+ let span_ctxt = expr.span.ctxt();
+ if unwrappable.branch.span.ctxt() == span_ctxt;
+ if unwrappable.check.span.ctxt() == span_ctxt;
+ then {
+ if call_to_unwrap == unwrappable.safe_to_unwrap {
+ let is_entire_condition = unwrappable.is_entire_condition;
+ let unwrappable_variable_name = self.cx.tcx.hir().name(unwrappable.local_id);
+ let suggested_pattern = if call_to_unwrap {
+ unwrappable.kind.success_variant_pattern()
+ } else {
+ unwrappable.kind.error_variant_pattern()
+ };
+
+ span_lint_hir_and_then(
+ self.cx,
+ UNNECESSARY_UNWRAP,
+ expr.hir_id,
+ expr.span,
+ &format!(
- unwrappable_variable_name,
++ "called `{}` on `{unwrappable_variable_name}` after checking its variant with `{}`",
+ method_name.ident.name,
- "if let {} = {}",
- suggested_pattern,
- unwrappable_variable_name,
+ unwrappable.check_name.ident.as_str(),
+ ),
+ |diag| {
+ if is_entire_condition {
+ diag.span_suggestion(
+ unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()),
+ "try",
+ format!(
++ "if let {suggested_pattern} = {unwrappable_variable_name}",
+ ),
+ // We don't track how the unwrapped value is used inside the
+ // block or suggest deleting the unwrap, so we can't offer a
+ // fixable solution.
+ Applicability::Unspecified,
+ );
+ } else {
+ diag.span_label(unwrappable.check.span, "the check is happening here");
+ diag.help("try using `if let` or `match`");
+ }
+ },
+ );
+ } else {
+ span_lint_hir_and_then(
+ self.cx,
+ PANICKING_UNWRAP,
+ expr.hir_id,
+ expr.span,
+ &format!("this call to `{}()` will always panic",
+ method_name.ident.name),
+ |diag| { diag.span_label(unwrappable.check.span, "because of this check"); },
+ );
+ }
+ }
+ }
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+}
+
+declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]);
+
+impl<'tcx> LateLintPass<'tcx> for Unwrap {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ fn_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ let mut v = UnwrappableVariablesVisitor {
+ cx,
+ unwrappables: Vec::new(),
+ };
+
+ walk_fn(&mut v, kind, decl, body.id(), fn_id);
+ }
+}
--- /dev/null
- use rustc_hir::intravisit::{self, Visitor};
- use rustc_hir::{Expr, ImplItemKind};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::ty::is_type_diagnostic_item;
++use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{method_chain_args, return_ty};
++use core::ops::ControlFlow;
+use if_chain::if_chain;
+use rustc_hir as hir;
- use rustc_middle::ty;
++use rustc_hir::ImplItemKind;
+use rustc_lint::{LateContext, LateLintPass};
- struct FindExpectUnwrap<'a, 'tcx> {
- lcx: &'a LateContext<'tcx>,
- typeck_results: &'tcx ty::TypeckResults<'tcx>,
- result: Vec<Span>,
- }
-
- impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- // check for `expect`
- if let Some(arglists) = method_chain_args(expr, &["expect"]) {
- let 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);
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions of type `Result` that contain `expect()` or `unwrap()`
+ ///
+ /// ### Why is this bad?
+ /// These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics.
+ ///
+ /// ### Known problems
+ /// This can cause false positives in functions that handle both recoverable and non recoverable errors.
+ ///
+ /// ### Example
+ /// Before:
+ /// ```rust
+ /// fn divisible_by_3(i_str: String) -> Result<(), String> {
+ /// let i = i_str
+ /// .parse::<i32>()
+ /// .expect("cannot divide the input by three");
+ ///
+ /// if i % 3 != 0 {
+ /// Err("Number is not divisible by 3")?
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// After:
+ /// ```rust
+ /// fn divisible_by_3(i_str: String) -> Result<(), String> {
+ /// let i = i_str
+ /// .parse::<i32>()
+ /// .map_err(|e| format!("cannot divide the input by three: {}", e))?;
+ ///
+ /// if i % 3 != 0 {
+ /// Err("Number is not divisible by 3")?
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[clippy::version = "1.48.0"]
+ pub UNWRAP_IN_RESULT,
+ restriction,
+ "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`"
+}
+
+declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if_chain! {
+ // first check if it's a method or function
+ if let hir::ImplItemKind::Fn(ref _signature, _) = impl_item.kind;
+ // checking if its return type is `result` or `option`
+ if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Result)
+ || is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Option);
+ then {
+ lint_impl_body(cx, impl_item.span, impl_item);
+ }
+ }
+ }
+}
+
- }
++fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
++ if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
++ let body = cx.tcx.hir().body(body_id);
++ let typeck = cx.tcx.typeck(impl_item.def_id.def_id);
++ let mut result = Vec::new();
++ let _: Option<!> = for_each_expr(body.value, |e| {
++ // check for `expect`
++ if let Some(arglists) = method_chain_args(e, &["expect"]) {
++ let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
++ if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
++ || is_type_diagnostic_item(cx, receiver_ty, sym::Result)
++ {
++ result.push(e.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);
+
- }
++ // check for `unwrap`
++ if let Some(arglists) = method_chain_args(e, &["unwrap"]) {
++ let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
++ if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
++ || is_type_diagnostic_item(cx, receiver_ty, sym::Result)
++ {
++ result.push(e.span);
++ }
+ }
- // and check sub-expressions
- intravisit::walk_expr(self, expr);
- }
- }
-
- fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
- if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
- let body = cx.tcx.hir().body(body_id);
- let mut fpu = FindExpectUnwrap {
- lcx: cx,
- typeck_results: cx.tcx.typeck(impl_item.def_id.def_id),
- result: Vec::new(),
- };
- fpu.visit_expr(body.value);
+
- if !fpu.result.is_empty() {
++ ControlFlow::Continue(())
++ });
+
+ // if we've found one, lint
- diag.span_note(fpu.result, "potential non-recoverable error(s)");
++ if !result.is_empty() {
+ span_lint_and_then(
+ cx,
+ UNWRAP_IN_RESULT,
+ impl_span,
+ "used unwrap or expect in a function that returns result or option",
+ move |diag| {
+ diag.help("unwrap and expect should not be used in a function that returns result or option");
++ diag.span_note(result, "potential non-recoverable error(s)");
+ },
+ );
+ }
+ }
+}
--- /dev/null
- &format!("name `{}` contains a capitalized acronym", ident),
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use itertools::Itertools;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for fully capitalized names and optionally names containing a capitalized acronym.
+ ///
+ /// ### Why is this bad?
+ /// In CamelCase, acronyms count as one word.
+ /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
+ /// for more.
+ ///
+ /// By default, the lint only triggers on fully-capitalized names.
+ /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
+ /// on all camel case names
+ ///
+ /// ### Known problems
+ /// When two acronyms are contiguous, the lint can't tell where
+ /// the first acronym ends and the second starts, so it suggests to lowercase all of
+ /// the letters in the second acronym.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct HTTPResponse;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct HttpResponse;
+ /// ```
+ #[clippy::version = "1.51.0"]
+ pub UPPER_CASE_ACRONYMS,
+ style,
+ "capitalized acronyms are against the naming convention"
+}
+
+#[derive(Default)]
+pub struct UpperCaseAcronyms {
+ avoid_breaking_exported_api: bool,
+ upper_case_acronyms_aggressive: bool,
+}
+
+impl UpperCaseAcronyms {
+ pub fn new(avoid_breaking_exported_api: bool, aggressive: bool) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ upper_case_acronyms_aggressive: aggressive,
+ }
+ }
+}
+
+impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
+
+fn correct_ident(ident: &str) -> String {
+ let ident = ident.chars().rev().collect::<String>();
+ let fragments = ident
+ .split_inclusive(|x: char| !x.is_ascii_lowercase())
+ .rev()
+ .map(|x| x.chars().rev().collect::<String>());
+
+ let mut ident = fragments.clone().next().unwrap();
+ for (ref prev, ref curr) in fragments.tuple_windows() {
+ if [prev, curr]
+ .iter()
+ .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase())
+ {
+ ident.push_str(&curr.to_ascii_lowercase());
+ } else {
+ ident.push_str(curr);
+ }
+ }
+ ident
+}
+
+fn check_ident(cx: &LateContext<'_>, ident: &Ident, be_aggressive: bool) {
+ let span = ident.span;
+ let ident = ident.as_str();
+ let corrected = correct_ident(ident);
+ // warn if we have pure-uppercase idents
+ // assume that two-letter words are some kind of valid abbreviation like FP for false positive
+ // (and don't warn)
+ if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
+ // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
+ // upper-case-acronyms-aggressive config option enabled
+ || (be_aggressive && ident != corrected)
+ {
+ span_lint_and_sugg(
+ cx,
+ UPPER_CASE_ACRONYMS,
+ span,
++ &format!("name `{ident}` contains a capitalized acronym"),
+ "consider making the acronym lowercase, except the initial letter",
+ corrected,
+ Applicability::MaybeIncorrect,
+ );
+ }
+}
+
+impl LateLintPass<'_> for UpperCaseAcronyms {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
+ // do not lint public items or in macros
+ if in_external_macro(cx.sess(), it.span)
+ || (self.avoid_breaking_exported_api && cx.access_levels.is_exported(it.def_id.def_id))
+ {
+ return;
+ }
+ match it.kind {
+ ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..) => {
+ check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
+ },
+ ItemKind::Enum(ref enumdef, _) => {
++ check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
+ // check enum variants separately because again we only want to lint on private enums and
+ // the fn check_variant does not know about the vis of the enum of its variants
+ enumdef
+ .variants
+ .iter()
+ .for_each(|variant| check_ident(cx, &variant.ident, self.upper_case_acronyms_aggressive));
+ },
+ _ => {},
+ }
+ }
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+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
+ /// - False positive with associated types in traits (#4140)
+ ///
+ /// ### 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 !is_from_proc_macro(cx, item); // expensive, should be last check
+ then {
+ StackItem::Check {
+ impl_id: item.def_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.def_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 {
+ 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)`)
+ ExprKind::Call(fun, _) => {
+ if let ExprKind::Path(QPath::Resolved(_, path)) = fun.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),
+ }
+ }
+ }
+ },
+ // unit enum variants (`Enum::A`)
+ ExprKind::Path(QPath::Resolved(_, path)) => lint_path_to_variant(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 {
+ 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),
+ _ => ()
+ }
+ }
+ }
+ }
+
+ 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 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
- use rustc_hir::{Expr, ExprKind, HirId, MatchSource};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
+use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- &format!("useless conversion to the same type: `{}`", b),
++use rustc_hir::{Expr, ExprKind, HirId, LangItem, MatchSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls
+ /// which uselessly convert to the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // format!() returns a `String`
+ /// let s: String = format!("hello").into();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let s: String = format!("hello");
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub USELESS_CONVERSION,
+ complexity,
+ "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type"
+}
+
+#[derive(Default)]
+pub struct UselessConversion {
+ try_desugar_arm: Vec<HirId>,
+}
+
+impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]);
+
+#[expect(clippy::too_many_lines)]
+impl<'tcx> LateLintPass<'tcx> for UselessConversion {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ if Some(&e.hir_id) == self.try_desugar_arm.last() {
+ return;
+ }
+
+ match e.kind {
+ ExprKind::Match(_, arms, MatchSource::TryDesugar) => {
+ let e = match arms[0].body.kind {
+ ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e)) => e,
+ _ => return,
+ };
+ if let ExprKind::Call(_, [arg, ..]) = e.kind {
+ self.try_desugar_arm.push(arg.hir_id);
+ }
+ },
+
+ ExprKind::MethodCall(name, recv, ..) => {
+ if is_trait_method(cx, e, sym::Into) && name.ident.as_str() == "into" {
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(recv);
+ if same_type_and_consts(a, b) {
+ let sugg = snippet_with_macro_callsite(cx, recv.span, "<expr>").to_string();
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
- &format!("useless conversion to the same type: `{}`", b),
++ &format!("useless conversion to the same type: `{b}`"),
+ "consider removing `.into()`",
+ sugg,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ if is_trait_method(cx, e, sym::IntoIterator) && name.ident.name == sym::into_iter {
+ if let Some(parent_expr) = get_parent_expr(cx, e) {
+ if let ExprKind::MethodCall(parent_name, ..) = parent_expr.kind {
+ if parent_name.ident.name != sym::into_iter {
+ return;
+ }
+ }
+ }
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(recv);
+ if same_type_and_consts(a, b) {
+ let sugg = snippet(cx, recv.span, "<expr>").into_owned();
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
- &format!("useless conversion to the same type: `{}`", b),
++ &format!("useless conversion to the same type: `{b}`"),
+ "consider removing `.into_iter()`",
+ sugg,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ if_chain! {
+ if is_trait_method(cx, e, sym::TryInto) && name.ident.name == sym::try_into;
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(recv);
+ if is_type_diagnostic_item(cx, a, sym::Result);
+ if let ty::Adt(_, substs) = a.kind();
+ if let Some(a_type) = substs.types().next();
+ if same_type_and_consts(a_type, b);
+
+ then {
+ span_lint_and_help(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
- &format!("useless conversion to the same type: `{}`", b),
++ &format!("useless conversion to the same type: `{b}`"),
+ None,
+ "consider removing `.try_into()`",
+ );
+ }
+ }
+ },
+
+ ExprKind::Call(path, [arg]) => {
+ if_chain! {
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ then {
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(arg);
+ if_chain! {
+ if match_def_path(cx, def_id, &paths::TRY_FROM);
+ if is_type_diagnostic_item(cx, a, sym::Result);
+ if let ty::Adt(_, substs) = a.kind();
+ if let Some(a_type) = substs.types().next();
+ if same_type_and_consts(a_type, b);
+
+ then {
+ let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from"));
+ span_lint_and_help(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
- if match_def_path(cx, def_id, &paths::FROM_FROM);
++ &format!("useless conversion to the same type: `{b}`"),
+ None,
+ &hint,
+ );
+ }
+ }
+
+ if_chain! {
- &format!("useless conversion to the same type: `{}`", b),
++ if cx.tcx.lang_items().require(LangItem::FromFrom).ok() == Some(def_id);
+ if same_type_and_consts(a, b);
+
+ then {
+ let sugg = Sugg::hir_with_macro_callsite(cx, arg, "<expr>").maybe_par();
+ let sugg_msg =
+ format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
++ &format!("useless conversion to the same type: `{b}`"),
+ &sugg_msg,
+ sugg.to_string(),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ }
+ }
+ },
+
+ _ => {},
+ }
+ }
+
+ fn check_expr_post(&mut self, _: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if Some(&e.hir_id) == self.try_desugar_arm.last() {
+ self.try_desugar_arm.pop();
+ }
+ }
+}
--- /dev/null
- other => write!(s, "/* unimplemented: {:?}*/", other).unwrap(),
+//! A group of attributes that can be attached to Rust code in order
+//! to generate a clippy lint detecting said code automatically.
+
+use clippy_utils::{get_attr, higher};
+use rustc_ast::ast::{LitFloatType, LitKind};
+use rustc_ast::LitIntType;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
+use rustc_hir::{
+ ArrayLen, BindingAnnotation, Closure, ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::{Ident, Symbol};
+use std::fmt::{Display, Formatter, Write as _};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Generates clippy code that detects the offending pattern
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // ./tests/ui/my_lint.rs
+ /// fn foo() {
+ /// // detect the following pattern
+ /// #[clippy::author]
+ /// if x == 42 {
+ /// // but ignore everything from here on
+ /// #![clippy::author = "ignore"]
+ /// }
+ /// ()
+ /// }
+ /// ```
+ ///
+ /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
+ /// a `./tests/ui/new_lint.stdout` file with the generated code:
+ ///
+ /// ```rust,ignore
+ /// // ./tests/ui/new_lint.stdout
+ /// if_chain! {
+ /// if let ExprKind::If(ref cond, ref then, None) = item.kind,
+ /// if let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind,
+ /// if let ExprKind::Path(ref path) = left.kind,
+ /// if let ExprKind::Lit(ref lit) = right.kind,
+ /// if let LitKind::Int(42, _) = lit.node,
+ /// then {
+ /// // report your lint here
+ /// }
+ /// }
+ /// ```
+ pub LINT_AUTHOR,
+ internal_warn,
+ "helper for writing lints"
+}
+
+declare_lint_pass!(Author => [LINT_AUTHOR]);
+
+/// Writes a line of output with indentation added
+macro_rules! out {
+ ($($t:tt)*) => {
+ println!(" {}", format_args!($($t)*))
+ };
+}
+
+/// The variables passed in are replaced with `&Binding`s where the `value` field is set
+/// to the original value of the variable. The `name` field is set to the name of the variable
+/// (using `stringify!`) and is adjusted to avoid duplicate names.
+/// Note that the `Binding` may be printed directly to output the `name`.
+macro_rules! bind {
+ ($self:ident $(, $name:ident)+) => {
+ $(let $name = & $self.bind(stringify!($name), $name);)+
+ };
+}
+
+/// Transforms the given `Option<T>` variables into `OptionPat<Binding<T>>`.
+/// This displays as `Some($name)` or `None` when printed. The name of the inner binding
+/// is set to the name of the variable passed to the macro.
+macro_rules! opt_bind {
+ ($self:ident $(, $name:ident)+) => {
+ $(let $name = OptionPat::new($name.map(|o| $self.bind(stringify!($name), o)));)+
+ };
+}
+
+/// Creates a `Binding` that accesses the field of an existing `Binding`
+macro_rules! field {
+ ($binding:ident.$field:ident) => {
+ &Binding {
+ name: $binding.name.to_string() + stringify!(.$field),
+ value: $binding.value.$field,
+ }
+ };
+}
+
+fn prelude() {
+ println!("if_chain! {{");
+}
+
+fn done() {
+ println!(" then {{");
+ println!(" // report your lint here");
+ println!(" }}");
+ println!("}}");
+}
+
+impl<'tcx> LateLintPass<'tcx> for Author {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ check_item(cx, item.hir_id());
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ check_item(cx, item.hir_id());
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ check_item(cx, item.hir_id());
+ }
+
+ fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
+ check_node(cx, arm.hir_id, |v| {
+ v.arm(&v.bind("arm", arm));
+ });
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ check_node(cx, expr.hir_id, |v| {
+ v.expr(&v.bind("expr", expr));
+ });
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Expr(e) | StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return,
+ _ => {},
+ }
+ check_node(cx, stmt.hir_id, |v| {
+ v.stmt(&v.bind("stmt", stmt));
+ });
+ }
+}
+
+fn check_item(cx: &LateContext<'_>, hir_id: HirId) {
+ let hir = cx.tcx.hir();
+ if let Some(body_id) = hir.maybe_body_owned_by(hir_id.expect_owner().def_id) {
+ check_node(cx, hir_id, |v| {
+ v.expr(&v.bind("expr", hir.body(body_id).value));
+ });
+ }
+}
+
+fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_, '_>)) {
+ if has_attr(cx, hir_id) {
+ prelude();
+ f(&PrintVisitor::new(cx));
+ done();
+ }
+}
+
+struct Binding<T> {
+ name: String,
+ value: T,
+}
+
+impl<T> Display for Binding<T> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.write_str(&self.name)
+ }
+}
+
+struct OptionPat<T> {
+ pub opt: Option<T>,
+}
+
+impl<T> OptionPat<T> {
+ fn new(opt: Option<T>) -> Self {
+ Self { opt }
+ }
+
+ fn if_some(&self, f: impl Fn(&T)) {
+ if let Some(t) = &self.opt {
+ f(t);
+ }
+ }
+}
+
+impl<T: Display> Display for OptionPat<T> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match &self.opt {
+ None => f.write_str("None"),
+ Some(node) => write!(f, "Some({node})"),
+ }
+ }
+}
+
+struct PrintVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ /// Fields are the current index that needs to be appended to pattern
+ /// binding names
+ ids: std::cell::Cell<FxHashMap<&'static str, u32>>,
+}
+
+#[allow(clippy::unused_self)]
+impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ ids: std::cell::Cell::default(),
+ }
+ }
+
+ fn next(&self, s: &'static str) -> String {
+ let mut ids = self.ids.take();
+ let out = match *ids.entry(s).and_modify(|n| *n += 1).or_default() {
+ // first usage of the name, use it as is
+ 0 => s.to_string(),
+ // append a number starting with 1
+ n => format!("{s}{n}"),
+ };
+ self.ids.set(ids);
+ out
+ }
+
+ fn bind<T>(&self, name: &'static str, value: T) -> Binding<T> {
+ let name = self.next(name);
+ Binding { name, value }
+ }
+
+ fn option<T: Copy>(&self, option: &Binding<Option<T>>, name: &'static str, f: impl Fn(&Binding<T>)) {
+ match option.value {
+ None => out!("if {option}.is_none();"),
+ Some(value) => {
+ let value = &self.bind(name, value);
+ out!("if let Some({value}) = {option};");
+ f(value);
+ },
+ }
+ }
+
+ fn slice<T>(&self, slice: &Binding<&[T]>, f: impl Fn(&Binding<&T>)) {
+ if slice.value.is_empty() {
+ out!("if {slice}.is_empty();");
+ } else {
+ out!("if {slice}.len() == {};", slice.value.len());
+ for (i, value) in slice.value.iter().enumerate() {
+ let name = format!("{slice}[{i}]");
+ f(&Binding { name, value });
+ }
+ }
+ }
+
+ fn destination(&self, destination: &Binding<hir::Destination>) {
+ self.option(field!(destination.label), "label", |label| {
+ self.ident(field!(label.ident));
+ });
+ }
+
+ fn ident(&self, ident: &Binding<Ident>) {
+ out!("if {ident}.as_str() == {:?};", ident.value.as_str());
+ }
+
+ fn symbol(&self, symbol: &Binding<Symbol>) {
+ out!("if {symbol}.as_str() == {:?};", symbol.value.as_str());
+ }
+
+ fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
+ if let QPath::LangItem(lang_item, ..) = *qpath.value {
+ out!("if matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _));");
+ } else {
+ out!("if match_qpath({qpath}, &[{}]);", path_to_string(qpath.value));
+ }
+ }
+
+ fn lit(&self, lit: &Binding<&Lit>) {
+ let kind = |kind| out!("if let LitKind::{kind} = {lit}.node;");
+ macro_rules! kind {
+ ($($t:tt)*) => (kind(format_args!($($t)*)));
+ }
+
+ match lit.value.node {
+ LitKind::Bool(val) => kind!("Bool({val:?})"),
+ LitKind::Char(c) => kind!("Char({c:?})"),
+ LitKind::Err => kind!("Err"),
+ LitKind::Byte(b) => kind!("Byte({b})"),
+ LitKind::Int(i, suffix) => {
+ let int_ty = match suffix {
+ LitIntType::Signed(int_ty) => format!("LitIntType::Signed(IntTy::{int_ty:?})"),
+ LitIntType::Unsigned(uint_ty) => format!("LitIntType::Unsigned(UintTy::{uint_ty:?})"),
+ LitIntType::Unsuffixed => String::from("LitIntType::Unsuffixed"),
+ };
+ kind!("Int({i}, {int_ty})");
+ },
+ LitKind::Float(_, suffix) => {
+ let float_ty = match suffix {
+ LitFloatType::Suffixed(suffix_ty) => format!("LitFloatType::Suffixed(FloatTy::{suffix_ty:?})"),
+ LitFloatType::Unsuffixed => String::from("LitFloatType::Unsuffixed"),
+ };
+ kind!("Float(_, {float_ty})");
+ },
+ LitKind::ByteStr(ref vec) => {
+ bind!(self, vec);
+ kind!("ByteStr(ref {vec})");
+ out!("if let [{:?}] = **{vec};", vec.value);
+ },
+ LitKind::Str(s, _) => {
+ bind!(self, s);
+ kind!("Str({s}, _)");
+ self.symbol(s);
+ },
+ }
+ }
+
+ fn arm(&self, arm: &Binding<&hir::Arm<'_>>) {
+ self.pat(field!(arm.pat));
+ match arm.value.guard {
+ None => out!("if {arm}.guard.is_none();"),
+ Some(hir::Guard::If(expr)) => {
+ bind!(self, expr);
+ out!("if let Some(Guard::If({expr})) = {arm}.guard;");
+ self.expr(expr);
+ },
+ Some(hir::Guard::IfLet(let_expr)) => {
+ bind!(self, let_expr);
+ out!("if let Some(Guard::IfLet({let_expr}) = {arm}.guard;");
+ self.pat(field!(let_expr.pat));
+ self.expr(field!(let_expr.init));
+ },
+ }
+ self.expr(field!(arm.body));
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn expr(&self, expr: &Binding<&hir::Expr<'_>>) {
+ if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) {
+ bind!(self, condition, body);
+ out!(
+ "if let Some(higher::While {{ condition: {condition}, body: {body} }}) \
+ = higher::While::hir({expr});"
+ );
+ self.expr(condition);
+ self.expr(body);
+ return;
+ }
+
+ if let Some(higher::WhileLet {
+ let_pat,
+ let_expr,
+ if_then,
+ }) = higher::WhileLet::hir(expr.value)
+ {
+ bind!(self, let_pat, let_expr, if_then);
+ out!(
+ "if let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
+ = higher::WhileLet::hir({expr});"
+ );
+ self.pat(let_pat);
+ self.expr(let_expr);
+ self.expr(if_then);
+ return;
+ }
+
+ if let Some(higher::ForLoop { pat, arg, body, .. }) = higher::ForLoop::hir(expr.value) {
+ bind!(self, pat, arg, body);
+ out!(
+ "if let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
+ = higher::ForLoop::hir({expr});"
+ );
+ self.pat(pat);
+ self.expr(arg);
+ self.expr(body);
+ return;
+ }
+
+ let kind = |kind| out!("if let ExprKind::{kind} = {expr}.kind;");
+ macro_rules! kind {
+ ($($t:tt)*) => (kind(format_args!($($t)*)));
+ }
+
+ match expr.value.kind {
+ ExprKind::Let(let_expr) => {
+ bind!(self, let_expr);
+ kind!("Let({let_expr})");
+ self.pat(field!(let_expr.pat));
+ // Does what ExprKind::Cast does, only adds a clause for the type
+ // if it's a path
+ if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
+ bind!(self, qpath);
+ out!("if let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind;");
+ self.qpath(qpath);
+ }
+ self.expr(field!(let_expr.init));
+ },
+ ExprKind::Box(inner) => {
+ bind!(self, inner);
+ kind!("Box({inner})");
+ self.expr(inner);
+ },
+ ExprKind::Array(elements) => {
+ bind!(self, elements);
+ kind!("Array({elements})");
+ self.slice(elements, |e| self.expr(e));
+ },
+ ExprKind::Call(func, args) => {
+ bind!(self, func, args);
+ kind!("Call({func}, {args})");
+ self.expr(func);
+ self.slice(args, |e| self.expr(e));
+ },
+ ExprKind::MethodCall(method_name, receiver, args, _) => {
+ bind!(self, method_name, receiver, args);
+ kind!("MethodCall({method_name}, {receiver}, {args}, _)");
+ self.ident(field!(method_name.ident));
+ self.expr(receiver);
+ self.slice(args, |e| self.expr(e));
+ },
+ ExprKind::Tup(elements) => {
+ bind!(self, elements);
+ kind!("Tup({elements})");
+ self.slice(elements, |e| self.expr(e));
+ },
+ ExprKind::Binary(op, left, right) => {
+ bind!(self, op, left, right);
+ kind!("Binary({op}, {left}, {right})");
+ out!("if BinOpKind::{:?} == {op}.node;", op.value.node);
+ self.expr(left);
+ self.expr(right);
+ },
+ ExprKind::Unary(op, inner) => {
+ bind!(self, inner);
+ kind!("Unary(UnOp::{op:?}, {inner})");
+ self.expr(inner);
+ },
+ ExprKind::Lit(ref lit) => {
+ bind!(self, lit);
+ kind!("Lit(ref {lit})");
+ self.lit(lit);
+ },
+ ExprKind::Cast(expr, cast_ty) => {
+ bind!(self, expr, cast_ty);
+ kind!("Cast({expr}, {cast_ty})");
+ if let TyKind::Path(ref qpath) = cast_ty.value.kind {
+ bind!(self, qpath);
+ out!("if let TyKind::Path(ref {qpath}) = {cast_ty}.kind;");
+ self.qpath(qpath);
+ }
+ self.expr(expr);
+ },
+ ExprKind::Type(expr, _ty) => {
+ bind!(self, expr);
+ kind!("Type({expr}, _)");
+ self.expr(expr);
+ },
+ ExprKind::Loop(body, label, des, _) => {
+ bind!(self, body);
+ opt_bind!(self, label);
+ kind!("Loop({body}, {label}, LoopSource::{des:?}, _)");
+ self.block(body);
+ label.if_some(|l| self.ident(field!(l.ident)));
+ },
+ ExprKind::If(cond, then, else_expr) => {
+ bind!(self, cond, then);
+ opt_bind!(self, else_expr);
+ kind!("If({cond}, {then}, {else_expr})");
+ self.expr(cond);
+ self.expr(then);
+ else_expr.if_some(|e| self.expr(e));
+ },
+ ExprKind::Match(scrutinee, arms, des) => {
+ bind!(self, scrutinee, arms);
+ kind!("Match({scrutinee}, {arms}, MatchSource::{des:?})");
+ self.expr(scrutinee);
+ self.slice(arms, |arm| self.arm(arm));
+ },
+ ExprKind::Closure(&Closure {
+ capture_clause,
+ fn_decl,
+ body: body_id,
+ movability,
+ ..
+ }) => {
+ let movability = OptionPat::new(movability.map(|m| format!("Movability::{m:?}")));
+
+ let ret_ty = match fn_decl.output {
+ FnRetTy::DefaultReturn(_) => "FnRetTy::DefaultReturn(_)",
+ FnRetTy::Return(_) => "FnRetTy::Return(_ty)",
+ };
+
+ bind!(self, fn_decl, body_id);
+ kind!("Closure(CaptureBy::{capture_clause:?}, {fn_decl}, {body_id}, _, {movability})");
+ out!("if let {ret_ty} = {fn_decl}.output;");
+ self.body(body_id);
+ },
+ ExprKind::Yield(sub, source) => {
+ bind!(self, sub);
+ kind!("Yield(sub, YieldSource::{source:?})");
+ self.expr(sub);
+ },
+ ExprKind::Block(block, label) => {
+ bind!(self, block);
+ opt_bind!(self, label);
+ kind!("Block({block}, {label})");
+ self.block(block);
+ label.if_some(|l| self.ident(field!(l.ident)));
+ },
+ ExprKind::Assign(target, value, _) => {
+ bind!(self, target, value);
+ kind!("Assign({target}, {value}, _span)");
+ self.expr(target);
+ self.expr(value);
+ },
+ ExprKind::AssignOp(op, target, value) => {
+ bind!(self, op, target, value);
+ kind!("AssignOp({op}, {target}, {value})");
+ out!("if BinOpKind::{:?} == {op}.node;", op.value.node);
+ self.expr(target);
+ self.expr(value);
+ },
+ ExprKind::Field(object, field_name) => {
+ bind!(self, object, field_name);
+ kind!("Field({object}, {field_name})");
+ self.ident(field_name);
+ self.expr(object);
+ },
+ ExprKind::Index(object, index) => {
+ bind!(self, object, index);
+ kind!("Index({object}, {index})");
+ self.expr(object);
+ self.expr(index);
+ },
+ ExprKind::Path(ref qpath) => {
+ bind!(self, qpath);
+ kind!("Path(ref {qpath})");
+ self.qpath(qpath);
+ },
+ ExprKind::AddrOf(kind, mutability, inner) => {
+ bind!(self, inner);
+ kind!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
+ self.expr(inner);
+ },
+ ExprKind::Break(destination, value) => {
+ bind!(self, destination);
+ opt_bind!(self, value);
+ kind!("Break({destination}, {value})");
+ self.destination(destination);
+ value.if_some(|e| self.expr(e));
+ },
+ ExprKind::Continue(destination) => {
+ bind!(self, destination);
+ kind!("Continue({destination})");
+ self.destination(destination);
+ },
+ ExprKind::Ret(value) => {
+ opt_bind!(self, value);
+ kind!("Ret({value})");
+ value.if_some(|e| self.expr(e));
+ },
+ ExprKind::InlineAsm(_) => {
+ kind!("InlineAsm(_)");
+ out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
+ },
+ ExprKind::Struct(qpath, fields, base) => {
+ bind!(self, qpath, fields);
+ opt_bind!(self, base);
+ kind!("Struct({qpath}, {fields}, {base})");
+ self.qpath(qpath);
+ self.slice(fields, |field| {
+ self.ident(field!(field.ident));
+ self.expr(field!(field.expr));
+ });
+ base.if_some(|e| self.expr(e));
+ },
+ ExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
+ ExprKind::Repeat(value, length) => {
+ bind!(self, value, length);
+ kind!("Repeat({value}, {length})");
+ self.expr(value);
+ match length.value {
+ ArrayLen::Infer(..) => out!("if let ArrayLen::Infer(..) = length;"),
+ ArrayLen::Body(anon_const) => {
+ bind!(self, anon_const);
+ out!("if let ArrayLen::Body({anon_const}) = {length};");
+ self.body(field!(anon_const.body));
+ },
+ }
+ },
+ ExprKind::Err => kind!("Err"),
+ ExprKind::DropTemps(expr) => {
+ bind!(self, expr);
+ kind!("DropTemps({expr})");
+ self.expr(expr);
+ },
+ }
+ }
+
+ fn block(&self, block: &Binding<&hir::Block<'_>>) {
+ self.slice(field!(block.stmts), |stmt| self.stmt(stmt));
+ self.option(field!(block.expr), "trailing_expr", |expr| {
+ self.expr(expr);
+ });
+ }
+
+ fn body(&self, body_id: &Binding<hir::BodyId>) {
+ let expr = self.cx.tcx.hir().body(body_id.value).value;
+ bind!(self, expr);
+ out!("let {expr} = &cx.tcx.hir().body({body_id}).value;");
+ self.expr(expr);
+ }
+
+ fn pat(&self, pat: &Binding<&hir::Pat<'_>>) {
+ let kind = |kind| out!("if let PatKind::{kind} = {pat}.kind;");
+ macro_rules! kind {
+ ($($t:tt)*) => (kind(format_args!($($t)*)));
+ }
+
+ match pat.value.kind {
+ PatKind::Wild => kind!("Wild"),
+ PatKind::Binding(ann, _, name, sub) => {
+ bind!(self, name);
+ opt_bind!(self, sub);
+ let ann = match ann {
+ BindingAnnotation::NONE => "NONE",
+ BindingAnnotation::REF => "REF",
+ BindingAnnotation::MUT => "MUT",
+ BindingAnnotation::REF_MUT => "REF_MUT",
+ };
+ kind!("Binding(BindingAnnotation::{ann}, _, {name}, {sub})");
+ self.ident(name);
+ sub.if_some(|p| self.pat(p));
+ },
+ PatKind::Struct(ref qpath, fields, ignore) => {
+ bind!(self, qpath, fields);
+ kind!("Struct(ref {qpath}, {fields}, {ignore})");
+ self.qpath(qpath);
+ self.slice(fields, |field| {
+ self.ident(field!(field.ident));
+ self.pat(field!(field.pat));
+ });
+ },
+ PatKind::Or(fields) => {
+ bind!(self, fields);
+ kind!("Or({fields})");
+ self.slice(fields, |pat| self.pat(pat));
+ },
+ PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
+ bind!(self, qpath, fields);
+ kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
+ self.qpath(qpath);
+ self.slice(fields, |pat| self.pat(pat));
+ },
+ PatKind::Path(ref qpath) => {
+ bind!(self, qpath);
+ kind!("Path(ref {qpath})");
+ self.qpath(qpath);
+ },
+ PatKind::Tuple(fields, skip_pos) => {
+ bind!(self, fields);
+ kind!("Tuple({fields}, {skip_pos:?})");
+ self.slice(fields, |field| self.pat(field));
+ },
+ PatKind::Box(pat) => {
+ bind!(self, pat);
+ kind!("Box({pat})");
+ self.pat(pat);
+ },
+ PatKind::Ref(pat, muta) => {
+ bind!(self, pat);
+ kind!("Ref({pat}, Mutability::{muta:?})");
+ self.pat(pat);
+ },
+ PatKind::Lit(lit_expr) => {
+ bind!(self, lit_expr);
+ kind!("Lit({lit_expr})");
+ self.expr(lit_expr);
+ },
+ PatKind::Range(start, end, end_kind) => {
+ opt_bind!(self, start, end);
+ kind!("Range({start}, {end}, RangeEnd::{end_kind:?})");
+ start.if_some(|e| self.expr(e));
+ end.if_some(|e| self.expr(e));
+ },
+ PatKind::Slice(start, middle, end) => {
+ bind!(self, start, end);
+ opt_bind!(self, middle);
+ kind!("Slice({start}, {middle}, {end})");
+ middle.if_some(|p| self.pat(p));
+ self.slice(start, |pat| self.pat(pat));
+ self.slice(end, |pat| self.pat(pat));
+ },
+ }
+ }
+
+ fn stmt(&self, stmt: &Binding<&hir::Stmt<'_>>) {
+ let kind = |kind| out!("if let StmtKind::{kind} = {stmt}.kind;");
+ macro_rules! kind {
+ ($($t:tt)*) => (kind(format_args!($($t)*)));
+ }
+
+ match stmt.value.kind {
+ StmtKind::Local(local) => {
+ bind!(self, local);
+ kind!("Local({local})");
+ self.option(field!(local.init), "init", |init| {
+ self.expr(init);
+ });
+ self.pat(field!(local.pat));
+ },
+ StmtKind::Item(_) => kind!("Item(item_id)"),
+ StmtKind::Expr(e) => {
+ bind!(self, e);
+ kind!("Expr({e})");
+ self.expr(e);
+ },
+ StmtKind::Semi(e) => {
+ bind!(self, e);
+ kind!("Semi({e})");
+ self.expr(e);
+ },
+ }
+ }
+}
+
+fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ get_attr(cx.sess(), attrs, "author").count() > 0
+}
+
+fn path_to_string(path: &QPath<'_>) -> String {
+ fn inner(s: &mut String, path: &QPath<'_>) {
+ match *path {
+ QPath::Resolved(_, path) => {
+ for (i, segment) in path.segments.iter().enumerate() {
+ if i > 0 {
+ *s += ", ";
+ }
+ write!(s, "{:?}", segment.ident.as_str()).unwrap();
+ }
+ },
+ QPath::TypeRelative(ty, segment) => match &ty.kind {
+ hir::TyKind::Path(inner_path) => {
+ inner(s, inner_path);
+ *s += ", ";
+ write!(s, "{:?}", segment.ident.as_str()).unwrap();
+ },
++ other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
+ },
+ QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"),
+ }
+ }
+ let mut s = String::new();
+ inner(&mut s, path);
+ s
+}
--- /dev/null
- /// A single disallowed method, used by the `DISALLOWED_METHODS` lint.
+//! 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,
+}
+
- pub enum DisallowedMethod {
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
- impl DisallowedMethod {
++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
+ }
- /// A single disallowed type, used by the `DISALLOWED_TYPES` lint.
- #[derive(Clone, Debug, Deserialize)]
- #[serde(untagged)]
- pub enum DisallowedType {
- Simple(String),
- WithReason { path: String, reason: Option<String> },
+
- /// 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.
++ pub fn reason(&self) -> Option<&str> {
++ match self {
++ Self::WithReason {
++ reason: Some(reason), ..
++ } => Some(reason),
++ _ => 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),
- (disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()),
++ /// 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.
+ ///
+ /// 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_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
++ (disallowed_methods: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
+ /// Lint: DISALLOWED_TYPES.
+ ///
+ /// The list of disallowed types, written as fully qualified paths.
- (await_holding_invalid_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
++ (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: _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
- "{:separator_width$}{:field_width$}",
- " ",
- field,
- separator_width = SEPARATOR_WIDTH,
- field_width = column_width
++ (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 `expect` should be allowed in test functions
+ (allow_expect_in_tests: bool = false),
+ /// Lint: UNWRAP_USED.
+ ///
+ /// Whether `unwrap` should be allowed in test functions
+ (allow_unwrap_in_tests: bool = false),
+ /// Lint: DBG_MACRO.
+ ///
+ /// Whether `dbg!` should be allowed in test functions
+ (allow_dbg_in_tests: bool = false),
+ /// Lint: RESULT_LARGE_ERR
+ ///
+ /// The maximum size of the `Err`-variant in a `Result` returned from a function
+ (large_error_threshold: u64 = 128),
+}
+
+/// Search for the configuration file.
+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,
- write!(msg, "\n{}", suffix).unwrap();
++ "{: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::source::snippet;
+use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::macros::root_macro_call_first_node;
- def_path_res, higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path,
- method_calls, paths, peel_blocks_with_stmt, SpanlessEq,
++use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::ty::match_type;
+use clippy_utils::{
- use rustc_hir::def::{DefKind, Res};
++ def_path_res, higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_any_def_paths,
++ match_def_path, method_calls, paths, peel_blocks_with_stmt, peel_hir_expr_refs, SpanlessEq,
+};
+use if_chain::if_chain;
+use rustc_ast as ast;
+use rustc_ast::ast::{Crate, ItemKind, LitKind, ModKind, NodeId};
+use rustc_ast::visit::FnKind;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
- BinOpKind, Block, Closure, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty,
++use rustc_hir::def::{DefKind, Namespace, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_hir::intravisit::Visitor;
+use rustc_hir::{
- use rustc_middle::mir::interpret::ConstValue;
- use rustc_middle::ty::{self, fast_reject::SimplifiedTypeGen, subst::GenericArgKind, FloatTy};
++ BinOpKind, Block, Closure, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind,
+ TyKind, UnOp,
+};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::nested_filter;
- use rustc_span::symbol::Symbol;
++use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
++use rustc_middle::ty::{
++ self, fast_reject::SimplifiedTypeGen, subst::GenericArgKind, AssocKind, DefIdTree, FloatTy, Ty,
++};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
- use rustc_hir_analysis::hir_ty_to_ty;
++use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::{sym, BytePos, Span};
- /// Checks for calls to `utils::match_type()` on a type diagnostic item
- /// and suggests to use `utils::is_type_diagnostic_item()` instead.
+
+use std::borrow::{Borrow, Cow};
++use std::str;
+
+#[cfg(feature = "internal")]
+pub mod metadata_collector;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for various things we like to keep tidy in clippy.
+ ///
+ /// ### Why is this bad?
+ /// We like to pretend we're an example of tidy code.
+ ///
+ /// ### Example
+ /// Wrong ordering of the util::paths constants.
+ pub CLIPPY_LINTS_INTERNAL,
+ internal,
+ "various things that will negatively affect your clippy experience"
+}
+
+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 calls to `cx.span_lint*` and suggests to use the `utils::*`
+ /// variant of the function.
+ ///
+ /// ### Why is this bad?
+ /// The `utils::*` variants also add a link to the Clippy documentation to the
+ /// warning/error messages.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// cx.span_lint(LINT_NAME, "message");
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// utils::span_lint(cx, LINT_NAME, "message");
+ /// ```
+ pub COMPILER_LINT_FUNCTIONS,
+ internal,
+ "usage of the lint functions of the compiler instead of the utils::* variant"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `cx.outer().expn_data()` and suggests to use
+ /// the `cx.outer_expn_data()`
+ ///
+ /// ### Why is this bad?
+ /// `cx.outer_expn_data()` is faster and more concise.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// expr.span.ctxt().outer().expn_data()
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// expr.span.ctxt().outer_expn_data()
+ /// ```
+ pub OUTER_EXPN_EXPN_DATA,
+ internal,
+ "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Not an actual lint. This lint is only meant for testing our customized internal compiler
+ /// error message by calling `panic`.
+ ///
+ /// ### Why is this bad?
+ /// ICE in large quantities can damage your teeth
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// 🍦🍦🍦🍦🍦
+ /// ```
+ pub PRODUCE_ICE,
+ internal,
+ "this message should not appear anywhere as we ICE before and don't emit the lint"
+}
+
+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
+ /// Lints `span_lint_and_then` function calls, where the
+ /// closure argument has only one statement and that statement is a method
+ /// call to `span_suggestion`, `span_help`, `span_note` (using the same
+ /// span), `help` or `note`.
+ ///
+ /// These usages of `span_lint_and_then` should be replaced with one of the
+ /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
+ /// `span_lint_and_note`.
+ ///
+ /// ### Why is this bad?
+ /// Using the wrapper `span_lint_and_*` functions, is more
+ /// convenient, readable and less error prone.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_suggestion(
+ /// expr.span,
+ /// help_msg,
+ /// sugg.to_string(),
+ /// Applicability::MachineApplicable,
+ /// );
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_help(expr.span, help_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.help(help_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_note(expr.span, note_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.note(note_msg);
+ /// });
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// span_lint_and_sugg(
+ /// cx,
+ /// TEST_LINT,
+ /// expr.span,
+ /// lint_msg,
+ /// help_msg,
+ /// sugg.to_string(),
+ /// Applicability::MachineApplicable,
+ /// );
+ /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+ /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+ /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+ /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+ /// ```
+ pub COLLAPSIBLE_SPAN_LINT_CALLS,
+ internal,
+ "found collapsible `span_lint_and_then` calls"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
- /// `utils::is_type_diagnostic_item()` does not require hardcoded paths.
++ /// Checks for usages of def paths when a diagnostic item or a `LangItem` could be used.
+ ///
+ /// ### Why is this bad?
- pub MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
++ /// 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)
+ /// ```
- "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`"
++ pub UNNECESSARY_DEF_PATH,
+ internal,
- &format!("the lint `{}` is not added to any `LintPass`", lint_name),
++ "using a def path when a diagnostic item or a `LangItem` is available"
+}
+
+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_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"
+}
+
+declare_clippy_lint! {
+ /// Finds unidiomatic usage of `if_chain!`
+ pub IF_CHAIN_STYLE,
+ internal,
+ "non-idiomatic `if_chain!` usage"
+}
+
+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
+ /// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
+ ///
+ pub MISSING_MSRV_ATTR_IMPL,
+ internal,
+ "checking if all necessary steps were taken when adding a MSRV to a lint"
+}
+
+declare_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"
+}
+
+declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
+
+impl EarlyLintPass for ClippyLintsInternal {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
+ if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") {
+ if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind {
+ if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") {
+ if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind {
+ let mut last_name: Option<&str> = None;
+ for item in items {
+ let name = item.ident.as_str();
+ if let Some(last_name) = last_name {
+ if *last_name > *name {
+ span_lint(
+ cx,
+ CLIPPY_LINTS_INTERNAL,
+ item.span,
+ "this constant should be before the previous constant due to lexical \
+ ordering",
+ );
+ }
+ }
+ last_name = Some(name);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[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,
- fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool {
++ &format!("the lint `{lint_name}` is not added to any `LintPass`"),
+ );
+ }
+ }
+ }
+}
+
- &format!("please use the Clippy variant of this function: `{}`", sugg),
++fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, 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
+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()
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct CompilerLintFunctions {
+ map: FxHashMap<&'static str, &'static str>,
+}
+
+impl CompilerLintFunctions {
+ #[must_use]
+ pub fn new() -> Self {
+ let mut map = FxHashMap::default();
+ map.insert("span_lint", "utils::span_lint");
+ map.insert("struct_span_lint", "utils::span_lint");
+ map.insert("lint", "utils::span_lint");
+ map.insert("span_lint_note", "utils::span_lint_and_note");
+ map.insert("span_lint_help", "utils::span_lint_and_help");
+ Self { map }
+ }
+}
+
+impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if is_lint_allowed(cx, COMPILER_LINT_FUNCTIONS, expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, self_arg, _, _) = &expr.kind;
+ let fn_name = path.ident;
+ if let Some(sugg) = self.map.get(fn_name.as_str());
+ let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ if match_type(cx, ty, &paths::EARLY_CONTEXT)
+ || match_type(cx, ty, &paths::LATE_CONTEXT);
+ then {
+ span_lint_and_help(
+ cx,
+ COMPILER_LINT_FUNCTIONS,
+ path.ident.span,
+ "usage of a compiler lint function",
+ None,
- "span_lint_and_help({}, {}, {}, {}, {}, {})",
- and_then_snippets.cx,
- and_then_snippets.lint,
- and_then_snippets.span,
- and_then_snippets.msg,
- &option_span,
- help
++ &format!("please use the Clippy variant of this function: `{sugg}`"),
+ );
+ }
+ }
+ }
+}
+
+declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]);
+
+impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, OUTER_EXPN_EXPN_DATA, expr.hir_id) {
+ return;
+ }
+
+ let (method_names, arg_lists, spans) = method_calls(expr, 2);
+ let method_names: Vec<&str> = method_names.iter().map(Symbol::as_str).collect();
+ if_chain! {
+ if let ["expn_data", "outer_expn"] = method_names.as_slice();
+ let (self_arg, args)= arg_lists[1];
+ if args.is_empty();
+ let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ if match_type(cx, self_ty, &paths::SYNTAX_CONTEXT);
+ then {
+ span_lint_and_sugg(
+ cx,
+ OUTER_EXPN_EXPN_DATA,
+ spans[1].with_hi(expr.span.hi()),
+ "usage of `outer_expn().expn_data()`",
+ "try",
+ "outer_expn_data()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+declare_lint_pass!(ProduceIce => [PRODUCE_ICE]);
+
+impl EarlyLintPass for ProduceIce {
+ fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
+ assert!(!is_trigger_fn(fn_kind), "Would you like some help with that?");
+ }
+}
+
+fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
+ match fn_kind {
+ FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy",
+ FnKind::Closure(..) => false,
+ }
+}
+
+declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
+
+impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, COLLAPSIBLE_SPAN_LINT_CALLS, expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Call(func, and_then_args) = expr.kind;
+ if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
+ if and_then_args.len() == 5;
+ if let ExprKind::Closure(&Closure { body, .. }) = &and_then_args[4].kind;
+ let body = cx.tcx.hir().body(body);
+ let only_expr = peel_blocks_with_stmt(body.value);
+ if let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind;
+ if let ExprKind::Path(..) = recv.kind;
+ then {
+ let and_then_snippets = get_and_then_snippets(cx, and_then_args);
+ let mut sle = SpanlessEq::new(cx).deny_side_effects();
+ match ps.ident.as_str() {
+ "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[0]) => {
+ suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args));
+ },
+ "span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[0]) => {
+ let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
+ },
+ "span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[0]) => {
+ let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
+ },
+ "help" => {
+ let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
+ suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
+ }
+ "note" => {
+ let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
+ suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
+struct AndThenSnippets<'a> {
+ cx: Cow<'a, str>,
+ lint: Cow<'a, str>,
+ span: Cow<'a, str>,
+ msg: Cow<'a, str>,
+}
+
+fn get_and_then_snippets<'a, 'hir>(cx: &LateContext<'_>, and_then_snippets: &'hir [Expr<'hir>]) -> AndThenSnippets<'a> {
+ let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx");
+ let lint_snippet = snippet(cx, and_then_snippets[1].span, "..");
+ let span_snippet = snippet(cx, and_then_snippets[2].span, "span");
+ let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#);
+
+ AndThenSnippets {
+ cx: cx_snippet,
+ lint: lint_snippet,
+ span: span_snippet,
+ msg: msg_snippet,
+ }
+}
+
+struct SpanSuggestionSnippets<'a> {
+ help: Cow<'a, str>,
+ sugg: Cow<'a, str>,
+ applicability: Cow<'a, str>,
+}
+
+fn span_suggestion_snippets<'a, 'hir>(
+ cx: &LateContext<'_>,
+ span_call_args: &'hir [Expr<'hir>],
+) -> SpanSuggestionSnippets<'a> {
+ let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
+ let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
+
+ SpanSuggestionSnippets {
+ help: help_snippet,
+ sugg: sugg_snippet,
+ applicability: applicability_snippet,
+ }
+}
+
+fn suggest_suggestion(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
+) {
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
+ "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ span_suggestion_snippets.help,
+ span_suggestion_snippets.sugg,
+ span_suggestion_snippets.applicability
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn suggest_help(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ help: &str,
+ with_span: bool,
+) {
+ let option_span = if with_span {
+ format!("Some({})", and_then_snippets.span)
+ } else {
+ "None".to_string()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
- "span_lint_and_note({}, {}, {}, {}, {}, {})",
- and_then_snippets.cx,
- and_then_snippets.lint,
- and_then_snippets.span,
- and_then_snippets.msg,
- note_span,
- note
++ "span_lint_and_help({}, {}, {}, {}, {}, {help})",
++ and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span,
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn suggest_note(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ note: &str,
+ with_span: bool,
+) {
+ let note_span = if with_span {
+ format!("Some({})", and_then_snippets.span)
+ } else {
+ "None".to_string()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
- declare_lint_pass!(MatchTypeOnDiagItem => [MATCH_TYPE_ON_DIAGNOSTIC_ITEM]);
++ "span_lint_and_note({}, {}, {}, {}, {note_span}, {note})",
++ and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg,
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
- impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
++declare_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
+
- if is_lint_allowed(cx, MATCH_TYPE_ON_DIAGNOSTIC_ITEM, expr.hir_id) {
++#[allow(clippy::too_many_lines)]
++impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
- // Check if this is a call to utils::match_type()
- if let ExprKind::Call(fn_path, [context, ty, ty_path]) = expr.kind;
- if is_expr_path_def_path(cx, fn_path, &["clippy_utils", "ty", "match_type"]);
++ enum Item {
++ LangItem(Symbol),
++ 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 is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
- if let Some(segments) = path_to_matched_type(cx, ty_path);
- let segments: Vec<&str> = segments.iter().map(Symbol::as_str).collect();
- if let Some(ty_did) = def_path_res(cx, &segments[..]).opt_def_id();
- // Check if the matched type is a diagnostic item
- if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
++ if let ExprKind::Call(func, [cx_arg, def_arg, args@..]) = expr.kind;
++ 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
- // TODO: check paths constants from external crates.
- let cx_snippet = snippet(cx, context.span, "_");
- let ty_snippet = snippet(cx, ty.span, "_");
++ 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) = def_path_res(cx, &segments[..], None).opt_def_id();
+ then {
- span_lint_and_sugg(
++ // def_path_res will match field names before anything else, but for this we want to match
++ // inherent functions first.
++ let 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
++ };
+
- MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
++ // Check if the target item is a diagnostic item or LangItem.
++ 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(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) {
++ let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"], Some(Namespace::TypeNS)).def_id();
++ let item_name = cx.tcx.adt_def(lang_items).variants().iter().nth(lang_item).unwrap().name;
++ (
++ "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().require(LangItem::{item}).ok() == 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().require(LangItem::{item}).ok() == Some(id))",
++ ),
++ false,
++ ),
++ _ => return,
++ };
++
++ span_lint_and_then(
+ cx,
- "usage of `clippy_utils::ty::match_type()` on a type diagnostic item",
- "try",
- format!("clippy_utils::ty::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet, ty_snippet, item_name),
- Applicability::MaybeIncorrect,
++ UNNECESSARY_DEF_PATH,
+ expr.span,
- fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<Symbol>> {
- use rustc_hir::ItemKind;
-
- match &expr.kind {
- ExprKind::AddrOf(.., expr) => return path_to_matched_type(cx, expr),
- ExprKind::Path(qpath) => match cx.qpath_res(qpath, expr.hir_id) {
++ msg,
++ |diag| {
++ diag.span_suggestion(expr.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"
++ );
++ }
++ },
+ );
+ }
+ }
+ }
+}
+
- if let Some(Node::Local(local)) = cx.tcx.hir().find(parent_id) {
- if let Some(init) = local.init {
- return path_to_matched_type(cx, init);
- }
++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);
- Res::Def(DefKind::Const | DefKind::Static(..), def_id) => {
- if let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(def_id) {
- if let ItemKind::Const(.., body_id) | ItemKind::Static(.., body_id) = item.kind {
- let body = cx.tcx.hir().body(body_id);
- return path_to_matched_type(cx, body.value);
- }
- }
++ 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,
+ },
- ExprKind::Array(exprs) => {
- let segments: Vec<Symbol> = exprs
- .iter()
- .filter_map(|expr| {
- if let ExprKind::Lit(lit) = &expr.kind {
- if let LitKind::Str(sym, _) = lit.node {
- return Some(sym);
- }
++ _ => None,
+ },
- None
- })
- .collect();
-
- if segments.len() == exprs.len() {
- return Some(segments);
- }
- },
- _ => {},
++ ExprKind::Array(exprs) => 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
++ None
++ })
++ .collect(),
++ _ => 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() {
++ let &alloc = alloc.provenance().values().next()?;
++ if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc) {
++ (alloc.inner(), ty)
++ } else {
++ return None;
++ }
++ } else {
++ (alloc, ty)
++ };
+
- if def_path_res(cx, path) != Res::Err {
++ if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind()
++ && let ty::Ref(_, ty, Mutability::Not) = *ty.kind()
++ && ty.is_str()
++ {
++ alloc
++ .provenance()
++ .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
++ }
+}
+
+// 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 let Some(def_id) = def_path_res(cx, module).opt_def_id() {
++ if def_path_res(cx, path, None) != Res::Err {
+ 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));
+ for item_def_id in lang_items.items().iter().flatten().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
+}
+
+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");
+ }
+ }
+ }
+}
+
+#[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] {
++ if let Some(def_id) = def_path_res(cx, module, None).opt_def_id() {
+ 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
+ },
+ }
+ }
+}
+
+declare_lint_pass!(IfChainStyle => [IF_CHAIN_STYLE]);
+
+impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ let (local, after, if_chain_span) = if_chain! {
+ if let [Stmt { kind: StmtKind::Local(local), .. }, after @ ..] = block.stmts;
+ if let Some(if_chain_span) = is_expn_of(block.span, "if_chain");
+ then { (local, after, if_chain_span) } else { return }
+ };
+ if is_first_if_chain_expr(cx, block.hir_id, if_chain_span) {
+ span_lint(
+ cx,
+ IF_CHAIN_STYLE,
+ if_chain_local_span(cx, local, if_chain_span),
+ "`let` expression should be above the `if_chain!`",
+ );
+ } else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
+ span_lint(
+ cx,
+ IF_CHAIN_STYLE,
+ if_chain_local_span(cx, local, if_chain_span),
+ "`let` expression should be inside `then { .. }`",
+ );
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ let (cond, then, els) = if let Some(higher::IfOrIfLet { cond, r#else, then }) = higher::IfOrIfLet::hir(expr) {
+ (cond, then, r#else.is_some())
+ } else {
+ return;
+ };
+ let then_block = match then.kind {
+ ExprKind::Block(block, _) => block,
+ _ => return,
+ };
+ let if_chain_span = is_expn_of(expr.span, "if_chain");
+ if !els {
+ check_nested_if_chains(cx, expr, then_block, if_chain_span);
+ }
+ let if_chain_span = match if_chain_span {
+ None => return,
+ Some(span) => span,
+ };
+ // check for `if a && b;`
+ if_chain! {
+ if let ExprKind::Binary(op, _, _) = cond.kind;
+ if op.node == BinOpKind::And;
+ if cx.sess().source_map().is_multiline(cond.span);
+ then {
+ span_lint(cx, IF_CHAIN_STYLE, cond.span, "`if a && b;` should be `if a; if b;`");
+ }
+ }
+ if is_first_if_chain_expr(cx, expr.hir_id, if_chain_span)
+ && is_if_chain_then(then_block.stmts, then_block.expr, if_chain_span)
+ {
+ span_lint(cx, IF_CHAIN_STYLE, expr.span, "`if_chain!` only has one `if`");
+ }
+ }
+}
+
+fn check_nested_if_chains(
+ cx: &LateContext<'_>,
+ if_expr: &Expr<'_>,
+ then_block: &Block<'_>,
+ if_chain_span: Option<Span>,
+) {
+ #[rustfmt::skip]
+ let (head, tail) = match *then_block {
+ Block { stmts, expr: Some(tail), .. } => (stmts, tail),
+ Block {
+ stmts: &[
+ ref head @ ..,
+ Stmt { kind: StmtKind::Expr(tail) | StmtKind::Semi(tail), .. }
+ ],
+ ..
+ } => (head, tail),
+ _ => return,
+ };
+ if_chain! {
+ if let Some(higher::IfOrIfLet { r#else: None, .. }) = higher::IfOrIfLet::hir(tail);
+ let sm = cx.sess().source_map();
+ if head
+ .iter()
+ .all(|stmt| matches!(stmt.kind, StmtKind::Local(..)) && !sm.is_multiline(stmt.span));
+ if if_chain_span.is_some() || !is_else_clause(cx.tcx, if_expr);
+ then {} else { return }
+ }
+ let (span, msg) = match (if_chain_span, is_expn_of(tail.span, "if_chain")) {
+ (None, Some(_)) => (if_expr.span, "this `if` can be part of the inner `if_chain!`"),
+ (Some(_), None) => (tail.span, "this `if` can be part of the outer `if_chain!`"),
+ (Some(a), Some(b)) if a != b => (b, "this `if_chain!` can be merged with the outer `if_chain!`"),
+ _ => return,
+ };
+ span_lint_and_then(cx, IF_CHAIN_STYLE, span, msg, |diag| {
+ let (span, msg) = match head {
+ [] => return,
+ [stmt] => (stmt.span, "this `let` statement can also be in the `if_chain!`"),
+ [a, .., b] => (
+ a.span.to(b.span),
+ "these `let` statements can also be in the `if_chain!`",
+ ),
+ };
+ diag.span_help(span, msg);
+ });
+}
+
+fn is_first_if_chain_expr(cx: &LateContext<'_>, hir_id: HirId, if_chain_span: Span) -> bool {
+ cx.tcx
+ .hir()
+ .parent_iter(hir_id)
+ .find(|(_, node)| {
+ #[rustfmt::skip]
+ !matches!(node, Node::Expr(Expr { kind: ExprKind::Block(..), .. }) | Node::Stmt(_))
+ })
+ .map_or(false, |(id, _)| {
+ is_expn_of(cx.tcx.hir().span(id), "if_chain") != Some(if_chain_span)
+ })
+}
+
+/// Checks a trailing slice of statements and expression of a `Block` to see if they are part
+/// of the `then {..}` portion of an `if_chain!`
+fn is_if_chain_then(stmts: &[Stmt<'_>], expr: Option<&Expr<'_>>, if_chain_span: Span) -> bool {
+ let span = if let [stmt, ..] = stmts {
+ stmt.span
+ } else if let Some(expr) = expr {
+ expr.span
+ } else {
+ // empty `then {}`
+ return true;
+ };
+ is_expn_of(span, "if_chain").map_or(true, |span| span != if_chain_span)
+}
+
+/// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
+fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: Span) -> Span {
+ let mut span = local.pat.span;
+ if let Some(init) = local.init {
+ span = span.to(init.span);
+ }
+ span.adjust(if_chain_span.ctxt().outer_expn());
+ let sm = cx.sess().source_map();
+ let span = sm.span_extend_to_prev_str(span, "let", false, true).unwrap_or(span);
+ let span = sm.span_extend_to_next_char(span, ';', false);
+ Span::new(
+ span.lo() - BytePos(3),
+ span.hi() + BytePos(1),
+ span.ctxt(),
+ span.parent(),
+ )
+}
+
+declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]);
+
+impl LateLintPass<'_> for MsrvAttrImpl {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if_chain! {
+ if let hir::ItemKind::Impl(hir::Impl {
+ of_trait: Some(lint_pass_trait_ref),
+ self_ty,
+ items,
+ ..
+ }) = &item.kind;
+ if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
+ let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
+ if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
+ let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
+ if let ty::Adt(self_ty_def, _) = self_ty.kind();
+ if self_ty_def.is_struct();
+ if self_ty_def.all_fields().any(|f| {
+ cx.tcx
+ .type_of(f.did)
+ .walk()
+ .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
+ .any(|t| match_type(cx, t.expect_ty(), &paths::RUSTC_VERSION))
+ });
+ if !items.iter().any(|item| item.ident.name == sym!(enter_lint_attrs));
+ then {
+ let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
+ let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
+ let span = cx.sess().source_map().span_through_char(item.span, '{');
+ span_lint_and_sugg(
+ cx,
+ MISSING_MSRV_ATTR_IMPL,
+ span,
+ &format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
+ &format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
+ format!("{}\n extract_msrv_attr!({context});", snippet(cx, span, "..")),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
--- /dev/null
-
- /// This template will be used to format the configuration section in the lint documentation.
- /// The `configurations` parameter will be replaced with one or multiple formatted
- /// `ClippyConfiguration` instances. See `CONFIGURATION_VALUE_TEMPLATE` for further customizations
- macro_rules! CONFIGURATION_SECTION_TEMPLATE {
- () => {
- r#"
- ### Configuration
- This lint has the following configuration variables:
-
- {configurations}
- "#
- };
- }
- /// This template will be used to format an individual `ClippyConfiguration` instance in the
- /// lint documentation.
- ///
- /// The format function will provide strings for the following parameters: `name`, `ty`, `doc` and
- /// `default`
- macro_rules! CONFIGURATION_VALUE_TEMPLATE {
- () => {
- "* `{name}`: `{ty}`: {doc} (defaults to `{default}`)\n"
- };
- }
-
- macro_rules! RENAMES_SECTION_TEMPLATE {
- () => {
- r#"
- ### Past names
-
- {names}
- "#
- };
- }
- macro_rules! RENAME_VALUE_TEMPLATE {
- () => {
- "* `{name}`\n"
- };
- }
-
+//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json
+//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
+//!
+//! This module and therefore the entire lint is guarded by a feature flag called `internal`
+//!
+//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
+//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
+//! a simple mistake)
+
+use crate::renamed_lints::RENAMED_LINTS;
+use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type};
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
+use clippy_utils::{last_path_segment, match_def_path, match_function_call, match_path, paths};
+use if_chain::if_chain;
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::{
+ self as hir, def::DefKind, intravisit, intravisit::Visitor, Closure, ExprKind, Item, ItemKind, Mutability, QPath,
+};
+use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
+use rustc_middle::hir::nested_filter;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Loc, Span, Symbol};
+use serde::{ser::SerializeStruct, Serialize, Serializer};
+use std::collections::BinaryHeap;
+use std::fmt;
+use std::fmt::Write as _;
+use std::fs::{self, OpenOptions};
+use std::io::prelude::*;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process::Command;
+
+/// This is the output file of the lint collector.
+const OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
+/// These lints are excluded from the export.
+const BLACK_LISTED_LINTS: &[&str] = &["lint_author", "dump_hir", "internal_metadata_collector"];
+/// These groups will be ignored by the lint group matcher. This is useful for collections like
+/// `clippy::all`
+const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
+/// Lints within this group will be excluded from the collection. These groups
+/// have to be defined without the `clippy::` prefix.
+const EXCLUDED_LINT_GROUPS: [&str; 1] = ["internal"];
+/// Collected deprecated lint will be assigned to this group in the JSON output
+const DEPRECATED_LINT_GROUP_STR: &str = "deprecated";
+/// This is the lint level for deprecated lints that will be displayed in the lint list
+const DEPRECATED_LINT_LEVEL: &str = "none";
+/// This array holds Clippy's lint groups with their corresponding default lint level. The
+/// lint level for deprecated lints is set in `DEPRECATED_LINT_LEVEL`.
+const DEFAULT_LINT_LEVELS: &[(&str, &str)] = &[
+ ("correctness", "deny"),
+ ("suspicious", "warn"),
+ ("restriction", "allow"),
+ ("style", "warn"),
+ ("pedantic", "allow"),
+ ("complexity", "warn"),
+ ("perf", "warn"),
+ ("cargo", "allow"),
+ ("nursery", "allow"),
+];
+/// This prefix is in front of the lint groups in the lint store. The prefix will be trimmed
+/// to only keep the actual lint group in the output.
+const CLIPPY_LINT_GROUP_PREFIX: &str = "clippy::";
- .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations))
+const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
+ &["clippy_utils", "diagnostics", "span_lint"],
+ &["clippy_utils", "diagnostics", "span_lint_and_help"],
+ &["clippy_utils", "diagnostics", "span_lint_and_note"],
+ &["clippy_utils", "diagnostics", "span_lint_hir"],
+ &["clippy_utils", "diagnostics", "span_lint_and_sugg"],
+ &["clippy_utils", "diagnostics", "span_lint_and_then"],
+ &["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
+];
+const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
+ ("span_suggestion", false),
+ ("span_suggestion_short", false),
+ ("span_suggestion_verbose", false),
+ ("span_suggestion_hidden", false),
+ ("tool_only_span_suggestion", false),
+ ("multipart_suggestion", true),
+ ("multipart_suggestions", true),
+ ("tool_only_multipart_suggestion", true),
+ ("span_suggestions", true),
+];
+const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
+ &["clippy_utils", "diagnostics", "multispan_sugg"],
+ &["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
+];
+const DEPRECATED_LINT_TYPE: [&str; 3] = ["clippy_lints", "deprecated_lints", "ClippyDeprecatedLint"];
+
+/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
+const APPLICABILITY_NAME_INDEX: usize = 2;
+/// This applicability will be set for unresolved applicability values.
+const APPLICABILITY_UNRESOLVED_STR: &str = "Unresolved";
+/// The version that will be displayed if none has been defined
+const VERSION_DEFAULT_STR: &str = "Unknown";
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Collects metadata about clippy lints for the website.
+ ///
+ /// This lint will be used to report problems of syntax parsing. You should hopefully never
+ /// see this but never say never I guess ^^
+ ///
+ /// ### Why is this bad?
+ /// This is not a bad thing but definitely a hacky way to do it. See
+ /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
+ /// about the implementation.
+ ///
+ /// ### Known problems
+ /// Hopefully none. It would be pretty uncool to have a problem here :)
+ ///
+ /// ### Example output
+ /// ```json,ignore
+ /// {
+ /// "id": "internal_metadata_collector",
+ /// "id_span": {
+ /// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
+ /// "line": 1
+ /// },
+ /// "group": "clippy::internal",
+ /// "docs": " ### What it does\nCollects metadata about clippy lints for the website. [...] "
+ /// }
+ /// ```
+ #[clippy::version = "1.56.0"]
+ pub INTERNAL_METADATA_COLLECTOR,
+ internal_warn,
+ "A busy bee collection metadata about lints"
+}
+
+impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Clone)]
+pub struct MetadataCollector {
+ /// All collected lints
+ ///
+ /// We use a Heap here to have the lints added in alphabetic order in the export
+ lints: BinaryHeap<LintMetadata>,
+ applicability_info: FxHashMap<String, ApplicabilityInfo>,
+ config: Vec<ClippyConfiguration>,
+ clippy_project_root: PathBuf,
+}
+
+impl MetadataCollector {
+ pub fn new() -> Self {
+ Self {
+ lints: BinaryHeap::<LintMetadata>::default(),
+ applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
+ config: collect_configs(),
+ clippy_project_root: std::env::current_dir()
+ .expect("failed to get current dir")
+ .ancestors()
+ .nth(1)
+ .expect("failed to get project root")
+ .to_path_buf(),
+ }
+ }
+
+ fn get_lint_configs(&self, lint_name: &str) -> Option<String> {
+ self.config
+ .iter()
+ .filter(|config| config.lints.iter().any(|lint| lint == lint_name))
+ .map(ToString::to_string)
+ .reduce(|acc, x| acc + &x)
- panic!("lint `{}` has an unterminated code block", lint_name)
++ .map(|configurations| {
++ format!(
++ r#"
++### Configuration
++This lint has the following configuration variables:
++
++{configurations}
++"#
++ )
++ })
+ }
+}
+
+impl Drop for MetadataCollector {
+ /// You might ask: How hacky is this?
+ /// My answer: YES
+ fn drop(&mut self) {
+ // The metadata collector gets dropped twice, this makes sure that we only write
+ // when the list is full
+ if self.lints.is_empty() {
+ return;
+ }
+
+ let mut applicability_info = std::mem::take(&mut self.applicability_info);
+
+ // Mapping the final data
+ let mut lints = std::mem::take(&mut self.lints).into_sorted_vec();
+ for x in &mut lints {
+ x.applicability = Some(applicability_info.remove(&x.id).unwrap_or_default());
+ replace_produces(&x.id, &mut x.docs, &self.clippy_project_root);
+ }
+
+ collect_renames(&mut lints);
+
+ // Outputting
+ if Path::new(OUTPUT_FILE).exists() {
+ fs::remove_file(OUTPUT_FILE).unwrap();
+ }
+ let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
+ writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
+ }
+}
+
+#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+struct LintMetadata {
+ id: String,
+ id_span: SerializableSpan,
+ group: String,
+ level: String,
+ docs: String,
+ version: String,
+ /// This field is only used in the output and will only be
+ /// mapped shortly before the actual output.
+ applicability: Option<ApplicabilityInfo>,
+}
+
+impl LintMetadata {
+ fn new(
+ id: String,
+ id_span: SerializableSpan,
+ group: String,
+ level: &'static str,
+ version: String,
+ docs: String,
+ ) -> Self {
+ Self {
+ id,
+ id_span,
+ group,
+ level: level.to_string(),
+ version,
+ docs,
+ applicability: None,
+ }
+ }
+}
+
+fn replace_produces(lint_name: &str, docs: &mut String, clippy_project_root: &Path) {
+ let mut doc_lines = docs.lines().map(ToString::to_string).collect::<Vec<_>>();
+ let mut lines = doc_lines.iter_mut();
+
+ 'outer: loop {
+ // Find the start of the example
+
+ // ```rust
+ loop {
+ match lines.next() {
+ Some(line) if line.trim_start().starts_with("```rust") => {
+ if line.contains("ignore") || line.contains("no_run") {
+ // A {{produces}} marker may have been put on a ignored code block by mistake,
+ // just seek to the end of the code block and continue checking.
+ if lines.any(|line| line.trim_start().starts_with("```")) {
+ continue;
+ }
+
- panic!(
- "lint `{}` has marker {{{{produces}}}} with an ignored or missing code block",
- lint_name
- )
++ panic!("lint `{lint_name}` has an unterminated code block")
+ }
+
+ break;
+ },
+ Some(line) if line.trim_start() == "{{produces}}" => {
- None => panic!("lint `{}` has an unterminated code block", lint_name),
++ panic!("lint `{lint_name}` has marker {{{{produces}}}} with an ignored or missing code block")
+ },
+ Some(line) => {
+ let line = line.trim();
+ // These are the two most common markers of the corrections section
+ if line.eq_ignore_ascii_case("Use instead:") || line.eq_ignore_ascii_case("Could be written as:") {
+ break 'outer;
+ }
+ },
+ None => break 'outer,
+ }
+ }
+
+ // Collect the example
+ let mut example = Vec::new();
+ loop {
+ match lines.next() {
+ Some(line) if line.trim_start() == "```" => break,
+ Some(line) => example.push(line),
- {}\n\
++ None => panic!("lint `{lint_name}` has an unterminated code block"),
+ }
+ }
+
+ // Find the {{produces}} and attempt to generate the output
+ loop {
+ match lines.next() {
+ Some(line) if line.is_empty() => {},
+ Some(line) if line.trim() == "{{produces}}" => {
+ let output = get_lint_output(lint_name, &example, clippy_project_root);
+ line.replace_range(
+ ..,
+ &format!(
+ "<details>\
+ <summary>Produces</summary>\n\
+ \n\
+ ```text\n\
- </details>",
- output
++ {output}\n\
+ ```\n\
- let prefixed_name = format!("{}{lint_name}", CLIPPY_LINT_GROUP_PREFIX);
++ </details>"
+ ),
+ );
+
+ break;
+ },
+ // No {{produces}}, we can move on to the next example
+ Some(_) => break,
+ None => break 'outer,
+ }
+ }
+ }
+
+ *docs = cleanup_docs(&doc_lines);
+}
+
+fn get_lint_output(lint_name: &str, example: &[&mut String], clippy_project_root: &Path) -> String {
+ let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("failed to create temp dir: {e}"));
+ let file = dir.path().join("lint_example.rs");
+
+ let mut source = String::new();
+ let unhidden = example
+ .iter()
+ .map(|line| line.trim_start().strip_prefix("# ").unwrap_or(line));
+
+ // Get any attributes
+ let mut lines = unhidden.peekable();
+ while let Some(line) = lines.peek() {
+ if line.starts_with("#!") {
+ source.push_str(line);
+ source.push('\n');
+ lines.next();
+ } else {
+ break;
+ }
+ }
+
+ let needs_main = !example.iter().any(|line| line.contains("fn main"));
+ if needs_main {
+ source.push_str("fn main() {\n");
+ }
+
+ for line in lines {
+ source.push_str(line);
+ source.push('\n');
+ }
+
+ if needs_main {
+ source.push_str("}\n");
+ }
+
+ if let Err(e) = fs::write(&file, &source) {
+ panic!("failed to write to `{}`: {e}", file.as_path().to_string_lossy());
+ }
+
- .unwrap_or_else(|e| panic!("failed to run `{:?}`: {e}", cmd));
++ let prefixed_name = format!("{CLIPPY_LINT_GROUP_PREFIX}{lint_name}");
+
+ let mut cmd = Command::new("cargo");
+
+ cmd.current_dir(clippy_project_root)
+ .env("CARGO_INCREMENTAL", "0")
+ .env("CLIPPY_ARGS", "")
+ .env("CLIPPY_DISABLE_DOCS_LINKS", "1")
+ // We need to disable this to enable all lints
+ .env("ENABLE_METADATA_COLLECTION", "0")
+ .args(["run", "--bin", "clippy-driver"])
+ .args(["--target-dir", "./clippy_lints/target"])
+ .args(["--", "--error-format=json"])
+ .args(["--edition", "2021"])
+ .arg("-Cdebuginfo=0")
+ .args(["-A", "clippy::all"])
+ .args(["-W", &prefixed_name])
+ .args(["-L", "./target/debug"])
+ .args(["-Z", "no-codegen"]);
+
+ let output = cmd
+ .arg(file.as_path())
+ .output()
- "did not find lint `{}` in output of example, got:\n{}\n{}",
- lint_name,
++ .unwrap_or_else(|e| panic!("failed to run `{cmd:?}`: {e}"));
+
+ let tmp_file_path = file.to_string_lossy();
+ let stderr = std::str::from_utf8(&output.stderr).unwrap();
+ let msgs = stderr
+ .lines()
+ .filter(|line| line.starts_with('{'))
+ .map(|line| serde_json::from_str(line).unwrap())
+ .collect::<Vec<serde_json::Value>>();
+
+ let mut rendered = String::new();
+ let iter = msgs
+ .iter()
+ .filter(|msg| matches!(&msg["code"]["code"], serde_json::Value::String(s) if s == &prefixed_name));
+
+ for message in iter {
+ let rendered_part = message["rendered"].as_str().expect("rendered field should exist");
+ rendered.push_str(rendered_part);
+ }
+
+ if rendered.is_empty() {
+ let rendered: Vec<&str> = msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect();
+ let non_json: Vec<&str> = stderr.lines().filter(|line| !line.starts_with('{')).collect();
+ panic!(
- write!(
++ "did not find lint `{lint_name}` in output of example, got:\n{}\n{}",
+ non_json.join("\n"),
+ rendered.join("\n")
+ );
+ }
+
+ // The reader doesn't need to see `/tmp/.tmpfiy2Qd/lint_example.rs` :)
+ rendered.trim_end().replace(&*tmp_file_path, "lint_example.rs")
+}
+
+#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+struct SerializableSpan {
+ path: String,
+ line: usize,
+}
+
+impl fmt::Display for SerializableSpan {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
+ }
+}
+
+impl SerializableSpan {
+ fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
+ Self::from_span(cx, item.ident.span)
+ }
+
+ fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
+ let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
+
+ Self {
+ path: format!("{}", loc.file.name.prefer_remapped()),
+ line: loc.line,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
+struct ApplicabilityInfo {
+ /// Indicates if any of the lint emissions uses multiple spans. This is related to
+ /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
+ /// currently not be applied automatically.
+ is_multi_part_suggestion: bool,
+ applicability: Option<usize>,
+}
+
+impl Serialize for ApplicabilityInfo {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?;
+ s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?;
+ if let Some(index) = self.applicability {
+ s.serialize_field(
+ "applicability",
+ &paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX],
+ )?;
+ } else {
+ s.serialize_field("applicability", APPLICABILITY_UNRESOLVED_STR)?;
+ }
+ s.end()
+ }
+}
+
+// ==================================================================
+// Configuration
+// ==================================================================
+#[derive(Debug, Clone, Default)]
+pub struct ClippyConfiguration {
+ name: String,
+ config_type: &'static str,
+ default: String,
+ lints: Vec<String>,
+ doc: String,
+ #[allow(dead_code)]
+ deprecation_reason: Option<&'static str>,
+}
+
+impl ClippyConfiguration {
+ pub fn new(
+ name: &'static str,
+ config_type: &'static str,
+ default: String,
+ doc_comment: &'static str,
+ deprecation_reason: Option<&'static str>,
+ ) -> Self {
+ let (lints, doc) = parse_config_field_doc(doc_comment)
+ .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
+
+ Self {
+ name: to_kebab(name),
+ lints,
+ doc,
+ config_type,
+ default,
+ deprecation_reason,
+ }
+ }
+}
+
+fn collect_configs() -> Vec<ClippyConfiguration> {
+ crate::utils::conf::metadata::get_configuration_metadata()
+}
+
+/// This parses the field documentation of the config struct.
+///
+/// ```rust, ignore
+/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
+/// ```
+///
+/// Would yield:
+/// ```rust, ignore
+/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
+/// ```
+fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
+ const DOC_START: &str = " Lint: ";
+ if_chain! {
+ if doc_comment.starts_with(DOC_START);
+ if let Some(split_pos) = doc_comment.find('.');
+ then {
+ let mut doc_comment = doc_comment.to_string();
+ let mut documentation = doc_comment.split_off(split_pos);
+
+ // Extract lints
+ doc_comment.make_ascii_lowercase();
+ let lints: Vec<String> = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect();
+
+ // Format documentation correctly
+ // split off leading `.` from lint name list and indent for correct formatting
+ documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");
+
+ Some((lints, documentation))
+ } else {
+ None
+ }
+ }
+}
+
+/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
+fn to_kebab(config_name: &str) -> String {
+ config_name.replace('_', "-")
+}
+
+impl fmt::Display for ClippyConfiguration {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
- CONFIGURATION_VALUE_TEMPLATE!(),
- name = self.name,
- ty = self.config_type,
- doc = self.doc,
- default = self.default
++ writeln!(
+ f,
- &format!("Unable to determine lint level for found group `{}`", group),
++ "* `{}`: `{}`: {} (defaults to `{}`)",
++ self.name, self.config_type, self.doc, self.default
+ )
+ }
+}
+
+// ==================================================================
+// Lint pass
+// ==================================================================
+impl<'hir> LateLintPass<'hir> for MetadataCollector {
+ /// Collecting lint declarations like:
+ /// ```rust, ignore
+ /// declare_clippy_lint! {
+ /// /// ### What it does
+ /// /// Something IDK.
+ /// pub SOME_LINT,
+ /// internal,
+ /// "Who am I?"
+ /// }
+ /// ```
+ fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
+ if let ItemKind::Static(ty, Mutability::Not, _) = item.kind {
+ // Normal lint
+ if_chain! {
+ // item validation
+ if is_lint_ref_type(cx, ty);
+ // disallow check
+ let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
+ if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
+ // metadata extraction
+ if let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item);
+ if let Some(mut raw_docs) = extract_attr_docs_or_lint(cx, item);
+ then {
+ if let Some(configuration_section) = self.get_lint_configs(&lint_name) {
+ raw_docs.push_str(&configuration_section);
+ }
+ let version = get_lint_version(cx, item);
+
+ self.lints.push(LintMetadata::new(
+ lint_name,
+ SerializableSpan::from_item(cx, item),
+ group,
+ level,
+ version,
+ raw_docs,
+ ));
+ }
+ }
+
+ if_chain! {
+ if is_deprecated_lint(cx, ty);
+ // disallow check
+ let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
+ if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
+ // Metadata the little we can get from a deprecated lint
+ if let Some(raw_docs) = extract_attr_docs_or_lint(cx, item);
+ then {
+ let version = get_lint_version(cx, item);
+
+ self.lints.push(LintMetadata::new(
+ lint_name,
+ SerializableSpan::from_item(cx, item),
+ DEPRECATED_LINT_GROUP_STR.to_string(),
+ DEPRECATED_LINT_LEVEL,
+ version,
+ raw_docs,
+ ));
+ }
+ }
+ }
+ }
+
+ /// Collecting constant applicability from the actual lint emissions
+ ///
+ /// Example:
+ /// ```rust, ignore
+ /// span_lint_and_sugg(
+ /// cx,
+ /// SOME_LINT,
+ /// item.span,
+ /// "Le lint message",
+ /// "Here comes help:",
+ /// "#![allow(clippy::all)]",
+ /// Applicability::MachineApplicable, // <-- Extracts this constant value
+ /// );
+ /// ```
+ fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
+ if let Some(args) = match_lint_emission(cx, expr) {
+ let emission_info = extract_emission_info(cx, args);
+ if emission_info.is_empty() {
+ // See:
+ // - src/misc.rs:734:9
+ // - src/methods/mod.rs:3545:13
+ // - src/methods/mod.rs:3496:13
+ // We are basically unable to resolve the lint name itself.
+ return;
+ }
+
+ for (lint_name, applicability, is_multi_part) in emission_info {
+ let app_info = self.applicability_info.entry(lint_name).or_default();
+ app_info.applicability = applicability;
+ app_info.is_multi_part_suggestion = is_multi_part;
+ }
+ }
+ }
+}
+
+// ==================================================================
+// Lint definition extraction
+// ==================================================================
+fn sym_to_string(sym: Symbol) -> String {
+ sym.as_str().to_string()
+}
+
+fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
+ extract_attr_docs(cx, item).or_else(|| {
+ lint_collection_error_item(cx, item, "could not collect the lint documentation");
+ None
+ })
+}
+
+/// This function collects all documentation that has been added to an item using
+/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
+///
+/// ```ignore
+/// #[doc = r"Hello world!"]
+/// #[doc = r"=^.^="]
+/// struct SomeItem {}
+/// ```
+///
+/// Would result in `Hello world!\n=^.^=\n`
+fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str);
+
+ if let Some(line) = lines.next() {
+ let raw_docs = lines.fold(String::from(line.as_str()) + "\n", |s, line| s + line.as_str() + "\n");
+ return Some(raw_docs);
+ }
+
+ None
+}
+
+/// This function may modify the doc comment to ensure that the string can be displayed using a
+/// markdown viewer in Clippy's lint list. The following modifications could be applied:
+/// * Removal of leading space after a new line. (Important to display tables)
+/// * Ensures that code blocks only contain language information
+fn cleanup_docs(docs_collection: &Vec<String>) -> String {
+ let mut in_code_block = false;
+ let mut is_code_block_rust = false;
+
+ let mut docs = String::new();
+ for line in docs_collection {
+ // Rustdoc hides code lines starting with `# ` and this removes them from Clippy's lint list :)
+ if is_code_block_rust && line.trim_start().starts_with("# ") {
+ continue;
+ }
+
+ // The line should be represented in the lint list, even if it's just an empty line
+ docs.push('\n');
+ if let Some(info) = line.trim_start().strip_prefix("```") {
+ in_code_block = !in_code_block;
+ is_code_block_rust = false;
+ if in_code_block {
+ let lang = info
+ .trim()
+ .split(',')
+ // remove rustdoc directives
+ .find(|&s| !matches!(s, "" | "ignore" | "no_run" | "should_panic"))
+ // if no language is present, fill in "rust"
+ .unwrap_or("rust");
+ docs.push_str("```");
+ docs.push_str(lang);
+
+ is_code_block_rust = lang == "rust";
+ continue;
+ }
+ }
+ // This removes the leading space that the macro translation introduces
+ if let Some(stripped_doc) = line.strip_prefix(' ') {
+ docs.push_str(stripped_doc);
+ } else if !line.is_empty() {
+ docs.push_str(line);
+ }
+ }
+
+ docs
+}
+
+fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String {
+ extract_clippy_version_value(cx, item).map_or_else(
+ || VERSION_DEFAULT_STR.to_string(),
+ |version| version.as_str().to_string(),
+ )
+}
+
+fn get_lint_group_and_level_or_lint(
+ cx: &LateContext<'_>,
+ lint_name: &str,
+ item: &Item<'_>,
+) -> Option<(String, &'static str)> {
+ let result = cx.lint_store.check_lint_name(
+ lint_name,
+ Some(sym::clippy),
+ &std::iter::once(Ident::with_dummy_span(sym::clippy)).collect(),
+ );
+ if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
+ if let Some(group) = get_lint_group(cx, lint_lst[0]) {
+ if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) {
+ return None;
+ }
+
+ if let Some(level) = get_lint_level_from_group(&group) {
+ Some((group, level))
+ } else {
+ lint_collection_error_item(
+ cx,
+ item,
- write!(collected, RENAME_VALUE_TEMPLATE!(), name = past_name).unwrap();
++ &format!("Unable to determine lint level for found group `{group}`"),
+ );
+ None
+ }
+ } else {
+ lint_collection_error_item(cx, item, "Unable to determine lint group");
+ None
+ }
+ } else {
+ lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
+ None
+ }
+}
+
+fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
+ for (group_name, lints, _) in cx.lint_store.get_lint_groups() {
+ if IGNORED_LINT_GROUPS.contains(&group_name) {
+ continue;
+ }
+
+ if lints.iter().any(|group_lint| *group_lint == lint_id) {
+ let group = group_name.strip_prefix(CLIPPY_LINT_GROUP_PREFIX).unwrap_or(group_name);
+ return Some((*group).to_string());
+ }
+ }
+
+ None
+}
+
+fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
+ DEFAULT_LINT_LEVELS
+ .iter()
+ .find_map(|(group_name, group_level)| (*group_name == lint_group).then_some(*group_level))
+}
+
+pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(ref path) = ty.kind {
+ if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) {
+ return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE);
+ }
+ }
+
+ false
+}
+
+fn collect_renames(lints: &mut Vec<LintMetadata>) {
+ for lint in lints {
+ let mut collected = String::new();
+ let mut names = vec![lint.id.clone()];
+
+ loop {
+ if let Some(lint_name) = names.pop() {
+ for (k, v) in RENAMED_LINTS {
+ if_chain! {
+ if let Some(name) = v.strip_prefix(CLIPPY_LINT_GROUP_PREFIX);
+ if name == lint_name;
+ if let Some(past_name) = k.strip_prefix(CLIPPY_LINT_GROUP_PREFIX);
+ then {
- write!(&mut lint.docs, RENAMES_SECTION_TEMPLATE!(), names = collected).unwrap();
++ writeln!(collected, "* `{past_name}`").unwrap();
+ names.push(past_name.to_string());
+ }
+ }
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ if !collected.is_empty() {
- &format!("metadata collection error for `{}`: {}", item.ident.name, message),
++ write!(
++ &mut lint.docs,
++ r#"
++### Past names
++
++{collected}
++"#
++ )
++ .unwrap();
+ }
+ }
+}
+
+// ==================================================================
+// Lint emission
+// ==================================================================
+fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
+ span_lint(
+ cx,
+ INTERNAL_METADATA_COLLECTOR,
+ item.ident.span,
++ &format!("metadata collection error for `{}`: {message}", item.ident.name),
+ );
+}
+
+// ==================================================================
+// Applicability
+// ==================================================================
+/// This function checks if a given expression is equal to a simple lint emission function call.
+/// It will return the function arguments if the emission matched any function.
+fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> {
+ LINT_EMISSION_FUNCTIONS
+ .iter()
+ .find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
+}
+
+fn take_higher_applicability(a: Option<usize>, b: Option<usize>) -> Option<usize> {
+ a.map_or(b, |a| a.max(b.unwrap_or_default()).into())
+}
+
+fn extract_emission_info<'hir>(
+ cx: &LateContext<'hir>,
+ args: &'hir [hir::Expr<'hir>],
+) -> Vec<(String, Option<usize>, bool)> {
+ let mut lints = Vec::new();
+ let mut applicability = None;
+ let mut multi_part = false;
+
+ for arg in args {
+ let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(arg));
+
+ if match_type(cx, arg_ty, &paths::LINT) {
+ // If we found the lint arg, extract the lint name
+ let mut resolved_lints = resolve_lints(cx, arg);
+ lints.append(&mut resolved_lints);
+ } else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
+ applicability = resolve_applicability(cx, arg);
+ } else if arg_ty.is_closure() {
+ multi_part |= check_is_multi_part(cx, arg);
+ applicability = applicability.or_else(|| resolve_applicability(cx, arg));
+ }
+ }
+
+ lints
+ .into_iter()
+ .map(|lint_name| (lint_name, applicability, multi_part))
+ .collect()
+}
+
+/// Resolves the possible lints that this expression could reference
+fn resolve_lints<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec<String> {
+ let mut resolver = LintResolver::new(cx);
+ resolver.visit_expr(expr);
+ resolver.lints
+}
+
+/// This function tries to resolve the linked applicability to the given expression.
+fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<usize> {
+ let mut resolver = ApplicabilityResolver::new(cx);
+ resolver.visit_expr(expr);
+ resolver.complete()
+}
+
+fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
+ if let ExprKind::Closure(&Closure { body, .. }) = closure_expr.kind {
+ let mut scanner = IsMultiSpanScanner::new(cx);
+ intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body));
+ return scanner.is_multi_part();
+ } else if let Some(local) = get_parent_local(cx, closure_expr) {
+ if let Some(local_init) = local.init {
+ return check_is_multi_part(cx, local_init);
+ }
+ }
+
+ false
+}
+
+struct LintResolver<'a, 'hir> {
+ cx: &'a LateContext<'hir>,
+ lints: Vec<String>,
+}
+
+impl<'a, 'hir> LintResolver<'a, 'hir> {
+ fn new(cx: &'a LateContext<'hir>) -> Self {
+ Self {
+ cx,
+ lints: Vec::<String>::default(),
+ }
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ if_chain! {
+ if let ExprKind::Path(qpath) = &expr.kind;
+ if let QPath::Resolved(_, path) = qpath;
+
+ let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
+ if match_type(self.cx, expr_ty, &paths::LINT);
+ then {
+ if let hir::def::Res::Def(DefKind::Static(..), _) = path.res {
+ let lint_name = last_path_segment(qpath).ident.name;
+ self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
+ } else if let Some(local) = get_parent_local(self.cx, expr) {
+ if let Some(local_init) = local.init {
+ intravisit::walk_expr(self, local_init);
+ }
+ }
+ }
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+}
+
+/// This visitor finds the highest applicability value in the visited expressions
+struct ApplicabilityResolver<'a, 'hir> {
+ cx: &'a LateContext<'hir>,
+ /// This is the index of highest `Applicability` for `paths::APPLICABILITY_VALUES`
+ applicability_index: Option<usize>,
+}
+
+impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> {
+ fn new(cx: &'a LateContext<'hir>) -> Self {
+ Self {
+ cx,
+ applicability_index: None,
+ }
+ }
+
+ fn add_new_index(&mut self, new_index: usize) {
+ self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index));
+ }
+
+ fn complete(self) -> Option<usize> {
+ self.applicability_index
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_path(&mut self, path: &'hir hir::Path<'hir>, _id: hir::HirId) {
+ for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
+ if match_path(path, enum_value) {
+ self.add_new_index(index);
+ return;
+ }
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
+
+ if_chain! {
+ if match_type(self.cx, expr_ty, &paths::APPLICABILITY);
+ if let Some(local) = get_parent_local(self.cx, expr);
+ if let Some(local_init) = local.init;
+ then {
+ intravisit::walk_expr(self, local_init);
+ }
+ };
+
+ intravisit::walk_expr(self, expr);
+ }
+}
+
+/// This returns the parent local node if the expression is a reference one
+fn get_parent_local<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Local<'hir>> {
+ if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
+ if let hir::def::Res::Local(local_hir) = path.res {
+ return get_parent_local_hir_id(cx, local_hir);
+ }
+ }
+
+ None
+}
+
+fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
+ let map = cx.tcx.hir();
+
+ match map.find(map.get_parent_node(hir_id)) {
+ Some(hir::Node::Local(local)) => Some(local),
+ Some(hir::Node::Pat(pattern)) => get_parent_local_hir_id(cx, pattern.hir_id),
+ _ => None,
+ }
+}
+
+/// This visitor finds the highest applicability value in the visited expressions
+struct IsMultiSpanScanner<'a, 'hir> {
+ cx: &'a LateContext<'hir>,
+ suggestion_count: usize,
+}
+
+impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
+ fn new(cx: &'a LateContext<'hir>) -> Self {
+ Self {
+ cx,
+ suggestion_count: 0,
+ }
+ }
+
+ /// Add a new single expression suggestion to the counter
+ fn add_single_span_suggestion(&mut self) {
+ self.suggestion_count += 1;
+ }
+
+ /// Signals that a suggestion with possible multiple spans was found
+ fn add_multi_part_suggestion(&mut self) {
+ self.suggestion_count += 2;
+ }
+
+ /// Checks if the suggestions include multiple spans
+ fn is_multi_part(&self) -> bool {
+ self.suggestion_count > 1
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ // Early return if the lint is already multi span
+ if self.is_multi_part() {
+ return;
+ }
+
+ match &expr.kind {
+ ExprKind::Call(fn_expr, _args) => {
+ let found_function = SUGGESTION_FUNCTIONS
+ .iter()
+ .any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
+ if found_function {
+ // These functions are all multi part suggestions
+ self.add_single_span_suggestion();
+ }
+ },
+ ExprKind::MethodCall(path, recv, _, _arg_span) => {
+ let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(recv));
+ if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
+ let called_method = path.ident.name.as_str().to_string();
+ for (method_name, is_multi_part) in &SUGGESTION_DIAGNOSTIC_BUILDER_METHODS {
+ if *method_name == called_method {
+ if *is_multi_part {
+ self.add_multi_part_suggestion();
+ } else {
+ self.add_single_span_suggestion();
+ }
+ break;
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+}
--- /dev/null
- format!("{}::{}", import_source_snippet, imports_string)
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_test_module_or_function;
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ def::{DefKind, Res},
+ Item, ItemKind, PathSegment, UseKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, BytePos};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `use Enum::*`.
+ ///
+ /// ### Why is this bad?
+ /// It is usually better style to use the prefixed name of
+ /// an enumeration variant, rather than importing variants.
+ ///
+ /// ### Known problems
+ /// Old-style enumerations that prefix the variants are
+ /// still around.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::cmp::Ordering::*;
+ ///
+ /// # fn foo(_: std::cmp::Ordering) {}
+ /// foo(Less);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// use std::cmp::Ordering;
+ ///
+ /// # fn foo(_: Ordering) {}
+ /// foo(Ordering::Less)
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_GLOB_USE,
+ pedantic,
+ "use items that import all variants of an enum"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard imports `use _::*`.
+ ///
+ /// ### Why is this bad?
+ /// wildcard imports can pollute the namespace. This is especially bad if
+ /// you try to import something through a wildcard, that already has been imported by name from
+ /// a different source:
+ ///
+ /// ```rust,ignore
+ /// use crate1::foo; // Imports a function named foo
+ /// use crate2::*; // Has a function named foo
+ ///
+ /// foo(); // Calls crate1::foo
+ /// ```
+ ///
+ /// This can lead to confusing error messages at best and to unexpected behavior at worst.
+ ///
+ /// ### Exceptions
+ /// Wildcard imports are allowed from modules named `prelude`. Many crates (including the standard library)
+ /// provide modules named "prelude" specifically designed for wildcard import.
+ ///
+ /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name.
+ ///
+ /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag.
+ ///
+ /// ### Known problems
+ /// If macros are imported through the wildcard, this macro is not included
+ /// by the suggestion and has to be added by hand.
+ ///
+ /// Applying the suggestion when explicit imports of the things imported with a glob import
+ /// exist, may result in `unused_imports` warnings.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use crate1::*;
+ ///
+ /// foo();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// use crate1::foo;
+ ///
+ /// foo();
+ /// ```
+ #[clippy::version = "1.43.0"]
+ pub WILDCARD_IMPORTS,
+ pedantic,
+ "lint `use _::*` statements"
+}
+
+#[derive(Default)]
+pub struct WildcardImports {
+ warn_on_all: bool,
+ test_modules_deep: u32,
+}
+
+impl WildcardImports {
+ pub fn new(warn_on_all: bool) -> Self {
+ Self {
+ warn_on_all,
+ test_modules_deep: 0,
+ }
+ }
+}
+
+impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
+
+impl LateLintPass<'_> for WildcardImports {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_test_module_or_function(cx.tcx, item) {
+ self.test_modules_deep = self.test_modules_deep.saturating_add(1);
+ }
+ let module = cx.tcx.parent_module_from_def_id(item.def_id.def_id);
+ if cx.tcx.visibility(item.def_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) {
+ return;
+ }
+ if_chain! {
+ if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind;
+ if self.warn_on_all || !self.check_exceptions(item, use_path.segments);
+ let used_imports = cx.tcx.names_imported_by_glob_use(item.def_id.def_id);
+ if !used_imports.is_empty(); // Already handled by `unused_imports`
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
+ let (span, braced_glob) = if import_source_snippet.is_empty() {
+ // This is a `_::{_, *}` import
+ // In this case `use_path.span` is empty and ends directly in front of the `*`,
+ // so we need to extend it by one byte.
+ (
+ use_path.span.with_hi(use_path.span.hi() + BytePos(1)),
+ true,
+ )
+ } else {
+ // In this case, the `use_path.span` ends right before the `::*`, so we need to
+ // extend it up to the `*`. Since it is hard to find the `*` in weird
+ // formattings like `use _ :: *;`, we extend it up to, but not including the
+ // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
+ // can just use the end of the item span
+ let mut span = use_path.span.with_hi(item.span.hi());
+ if snippet(cx, span, "").ends_with(';') {
+ span = use_path.span.with_hi(item.span.hi() - BytePos(1));
+ }
+ (
+ span, false,
+ )
+ };
+
+ let imports_string = if used_imports.len() == 1 {
+ used_imports.iter().next().unwrap().to_string()
+ } else {
+ let mut imports = used_imports
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>();
+ imports.sort();
+ if braced_glob {
+ imports.join(", ")
+ } else {
+ format!("{{{}}}", imports.join(", "))
+ }
+ };
+
+ let sugg = if braced_glob {
+ imports_string
+ } else {
++ format!("{import_source_snippet}::{imports_string}")
+ };
+
+ let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res {
+ (ENUM_GLOB_USE, "usage of wildcard import for enum variants")
+ } else {
+ (WILDCARD_IMPORTS, "usage of wildcard import")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ span,
+ message,
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+
+ fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_test_module_or_function(cx.tcx, item) {
+ self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
+ }
+ }
+}
+
+impl WildcardImports {
+ fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
+ item.span.from_expansion()
+ || is_prelude_import(segments)
+ || (is_super_only_import(segments) && self.test_modules_deep > 0)
+ }
+}
+
+// Allow "...prelude::..::*" imports.
+// Many crates have a prelude, and it is imported as a glob by design.
+fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
+ segments.iter().any(|ps| ps.ident.name == sym::prelude)
+}
+
+// Allow "super::*" imports in tests.
+fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
+ segments.len() == 1 && segments[0].ident.name == kw::Super
+}
--- /dev/null
- use clippy_utils::source::snippet_opt;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn, MacroCall};
- use rustc_span::{sym, BytePos, Span};
++use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
+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};
- if let Some(replacement) = replacement {
++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,
+}
+
+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");
+
+ match diag_name {
+ sym::print_macro | sym::println_macro => {
+ if !is_build_script {
+ span_lint(cx, PRINT_STDOUT, macro_call.span, &format!("use of `{name}!`"));
+ }
+ },
+ sym::eprint_macro | sym::eprintln_macro => {
+ 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| {
- let value_span = expand_past_previous_comma(cx, value.span);
-
++ if let Some(replacement) = replacement
+ // `format!("{}", "a")`, `format!("{named}", named = "b")
+ // ~~~~~ ~~~~~~~~~~~~~
-
- // Expand from `writeln!(o, "")` to `writeln!(o, "")`
- // ^^ ^^^^
- 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))
- }
++ && 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
- "consider using `{}::NAN` if you would like a constant representing NaN",
- float_type,
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint_and_help;
+use if_chain::if_chain;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `0.0 / 0.0`.
+ ///
+ /// ### Why is this bad?
+ /// It's less readable than `f32::NAN` or `f64::NAN`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let nan = 0.0f32 / 0.0;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let nan = f32::NAN;
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ZERO_DIVIDED_BY_ZERO,
+ complexity,
+ "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`"
+}
+
+declare_lint_pass!(ZeroDiv => [ZERO_DIVIDED_BY_ZERO]);
+
+impl<'tcx> LateLintPass<'tcx> for ZeroDiv {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // check for instances of 0.0/0.0
+ if_chain! {
+ if let ExprKind::Binary(ref op, left, right) = expr.kind;
+ if op.node == BinOpKind::Div;
+ // TODO - constant_simple does not fold many operations involving floats.
+ // That's probably fine for this lint - it's pretty unlikely that someone would
+ // do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too.
+ if let Some(lhs_value) = constant_simple(cx, cx.typeck_results(), left);
+ if let Some(rhs_value) = constant_simple(cx, cx.typeck_results(), right);
+ if Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value;
+ if Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value;
+ then {
+ // since we're about to suggest a use of f32::NAN or f64::NAN,
+ // match the precision of the literals that are given.
+ let float_type = match (lhs_value, rhs_value) {
+ (Constant::F64(_), _)
+ | (_, Constant::F64(_)) => "f64",
+ _ => "f32"
+ };
+ span_lint_and_help(
+ cx,
+ ZERO_DIVIDED_BY_ZERO,
+ expr.span,
+ "constant division of `0.0` with `0.0` will always result in NaN",
+ None,
+ &format!(
++ "consider using `{float_type}::NAN` if you would like a constant representing NaN",
+ ),
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir_analysis::hir_ty_to_ty;
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{is_normalizable, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_hir::{self as hir, HirId, ItemKind, Node};
++use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf as _;
+use rustc_middle::ty::{Adt, Ty, TypeVisitable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
- let second_parent_id = cx
- .tcx
- .hir()
- .get_parent_item(parent_id.into()).def_id;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for maps with zero-sized value types anywhere in the code.
+ ///
+ /// ### Why is this bad?
+ /// Since there is only a single value for a zero-sized type, a map
+ /// containing zero sized values is effectively a set. Using a set in that case improves
+ /// readability and communicates intent more clearly.
+ ///
+ /// ### Known problems
+ /// * A zero-sized type cannot be recovered later if it contains private fields.
+ /// * This lints the signature of public items
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// fn unique_words(text: &str) -> HashMap<&str, ()> {
+ /// todo!();
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// fn unique_words(text: &str) -> HashSet<&str> {
+ /// todo!();
+ /// }
+ /// ```
+ #[clippy::version = "1.50.0"]
+ pub ZERO_SIZED_MAP_VALUES,
+ pedantic,
+ "usage of map with zero-sized value type"
+}
+
+declare_lint_pass!(ZeroSizedMapValues => [ZERO_SIZED_MAP_VALUES]);
+
+impl LateLintPass<'_> for ZeroSizedMapValues {
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
+ if_chain! {
+ if !hir_ty.span.from_expansion();
+ if !in_trait_impl(cx, hir_ty.hir_id);
+ let ty = ty_from_hir_ty(cx, hir_ty);
+ if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap);
+ if let Adt(_, substs) = ty.kind();
+ let ty = substs.type_at(1);
+ // Fixes https://github.com/rust-lang/rust-clippy/issues/7447 because of
+ // https://github.com/rust-lang/rust/blob/master/compiler/rustc_middle/src/ty/sty.rs#L968
+ if !ty.has_escaping_bound_vars();
+ // Do this to prevent `layout_of` crashing, being unable to fully normalize `ty`.
+ if is_normalizable(cx, cx.param_env, ty);
+ if let Ok(layout) = cx.layout_of(ty);
+ if layout.is_zst();
+ then {
+ span_lint_and_help(cx, ZERO_SIZED_MAP_VALUES, hir_ty.span, "map with zero-sized value type", None, "consider using a set instead");
+ }
+ }
+ }
+}
+
+fn in_trait_impl(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ let parent_id = cx.tcx.hir().get_parent_item(hir_id);
++ let second_parent_id = cx.tcx.hir().get_parent_item(parent_id.into()).def_id;
+ if let Some(Node::Item(item)) = cx.tcx.hir().find_by_def_id(second_parent_id) {
+ if let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind {
+ return true;
+ }
+ }
+ false
+}
+
+fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Ty<'tcx> {
+ cx.maybe_typeck_results()
+ .and_then(|results| {
+ if results.hir_owner == hir_ty.hir_id.owner {
+ results.node_type_opt(hir_ty.hir_id)
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| hir_ty_to_ty(cx.tcx, hir_ty))
+}
--- /dev/null
- version = "0.1.65"
+[package]
+name = "clippy_utils"
++version = "0.1.66"
+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
- sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
+use rustc_ast::ast;
+use rustc_ast::attr;
+use rustc_errors::Applicability;
+use rustc_session::Session;
+use rustc_span::sym;
+use std::str::FromStr;
+
+/// Deprecation status of attributes known by Clippy.
+pub enum DeprecationStatus {
+ /// Attribute is deprecated
+ Deprecated,
+ /// Attribute is deprecated and was replaced by the named attribute
+ Replaced(&'static str),
+ None,
+}
+
+#[rustfmt::skip]
+pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
+ ("author", DeprecationStatus::None),
+ ("version", DeprecationStatus::None),
+ ("cognitive_complexity", DeprecationStatus::None),
+ ("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")),
+ ("dump", DeprecationStatus::None),
+ ("msrv", DeprecationStatus::None),
+ ("has_significant_drop", DeprecationStatus::None),
+];
+
+pub struct LimitStack {
+ stack: Vec<u64>,
+}
+
+impl Drop for LimitStack {
+ fn drop(&mut self) {
+ assert_eq!(self.stack.len(), 1);
+ }
+}
+
+impl LimitStack {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self { stack: vec![limit] }
+ }
+ pub fn limit(&self) -> u64 {
+ *self.stack.last().expect("there should always be a value in the stack")
+ }
+ pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| stack.push(val));
+ }
+ pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
+ let stack = &mut self.stack;
+ parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
+ }
+}
+
+pub fn get_attr<'a>(
+ sess: &'a Session,
+ attrs: &'a [ast::Attribute],
+ name: &'static str,
+) -> impl Iterator<Item = &'a ast::Attribute> {
+ attrs.iter().filter(move |attr| {
+ let attr = if let ast::AttrKind::Normal(ref normal) = attr.kind {
+ &normal.item
+ } else {
+ return false;
+ };
+ let attr_segments = &attr.path.segments;
+ if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
+ BUILTIN_ATTRIBUTES
+ .iter()
+ .find_map(|&(builtin_name, ref deprecation_status)| {
+ if attr_segments[1].ident.name.as_str() == builtin_name {
+ Some(deprecation_status)
+ } else {
+ None
+ }
+ })
+ .map_or_else(
+ || {
+ sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
+ false
+ },
+ |deprecation_status| {
+ let mut diag =
+ sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
+ match *deprecation_status {
+ DeprecationStatus::Deprecated => {
+ diag.emit();
+ false
+ },
+ DeprecationStatus::Replaced(new_name) => {
+ diag.span_suggestion(
+ attr_segments[1].ident.span,
+ "consider using",
+ new_name,
+ Applicability::MachineApplicable,
+ );
+ diag.emit();
+ false
+ },
+ DeprecationStatus::None => {
+ diag.cancel();
+ attr_segments[1].ident.name.as_str() == name
+ },
+ }
+ },
+ )
+ } else {
+ false
+ }
+ })
+}
+
+fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
+ for attr in get_attr(sess, attrs, name) {
+ if let Some(ref value) = attr.value_str() {
+ if let Ok(value) = FromStr::from_str(value.as_str()) {
+ f(value);
+ } else {
+ sess.span_err(attr.span, "not a number");
+ }
+ } else {
+ sess.span_err(attr.span, "bad clippy attribute");
+ }
+ }
+}
+
+pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
+ let mut unique_attr = None;
+ for attr in get_attr(sess, attrs, name) {
+ match attr.style {
+ ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
+ ast::AttrStyle::Inner => {
- sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
++ sess.struct_span_err(attr.span, &format!("`{name}` is defined multiple times"))
+ .span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
+ .emit();
+ },
+ ast::AttrStyle::Outer => {
++ sess.span_err(attr.span, &format!("`{name}` cannot be an outer attribute"));
+ },
+ }
+ }
+ unique_attr
+}
+
+/// Return true if the attributes contain any of `proc_macro`,
+/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
+pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
+}
+
+/// Return true if the attributes contain `#[doc(hidden)]`
+pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
+ attrs
+ .iter()
+ .filter(|attr| attr.has_name(sym::doc))
+ .filter_map(ast::Attribute::meta_item_list)
+ .any(|l| attr::list_contains_name(&l, sym::hidden))
+}
--- /dev/null
- "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
+//! 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!(
- }),
- lint
++ "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_help<'a, T: LintContext>(
+ cx: &'a 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_note<'a, T: LintContext>(
+ cx: &'a 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<'a, T: LintContext>(
+ cx: &'a 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
- Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (),
+//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
+//!
+//! Things to consider:
+//! - has the expression side-effects?
+//! - is the expression computationally expensive?
+//!
+//! See lints:
+//! - unnecessary-lazy-evaluations
+//! - or-fun-call
+//! - option-if-let-else
+
+use crate::ty::{all_predicates_of, is_copy};
+use crate::visitors::is_const_evaluatable;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, PredicateKind};
+use rustc_span::{sym, Symbol};
+use std::cmp;
+use std::ops;
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+enum EagernessSuggestion {
+ // The expression is cheap and should be evaluated eagerly
+ Eager,
+ // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to
+ // eager evaluation.
+ NoChange,
+ // The expression is likely expensive and should be evaluated lazily.
+ Lazy,
+ // The expression cannot be placed into a closure.
+ ForceNoChange,
+}
+impl ops::BitOr for EagernessSuggestion {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self {
+ cmp::max(self, rhs)
+ }
+}
+impl ops::BitOrAssign for EagernessSuggestion {
+ fn bitor_assign(&mut self, rhs: Self) {
+ *self = *self | rhs;
+ }
+}
+
+/// Determine the eagerness of the given function call.
+fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion {
+ use EagernessSuggestion::{Eager, Lazy, NoChange};
+ let name = name.as_str();
+
+ let ty = match cx.tcx.impl_of_method(fn_id) {
+ Some(id) => cx.tcx.type_of(id),
+ None => return Lazy,
+ };
+
+ if (name.starts_with("as_") || name == "len" || name == "is_empty") && have_one_arg {
+ if matches!(
+ cx.tcx.crate_name(fn_id.krate),
+ sym::std | sym::core | sym::alloc | sym::proc_macro
+ ) {
+ Eager
+ } else {
+ NoChange
+ }
+ } else if let ty::Adt(def, subs) = ty.kind() {
+ // Types where the only fields are generic types (or references to) with no trait bounds other
+ // than marker traits.
+ // Due to the limited operations on these types functions should be fairly cheap.
+ if def
+ .variants()
+ .iter()
+ .flat_map(|v| v.fields.iter())
+ .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
+ && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
+ PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker,
+ _ => true,
+ })
+ && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
+ {
+ // Limit the function to either `(self) -> bool` or `(&self) -> bool`
+ match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output {
+ [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
+ _ => Lazy,
+ }
+ } else {
+ Lazy
+ }
+ } else {
+ Lazy
+ }
+}
+
+#[expect(clippy::too_many_lines)]
+fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ eagerness: EagernessSuggestion,
+ }
+
+ impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
+ if self.eagerness == ForceNoChange {
+ return;
+ }
+ match e.kind {
+ ExprKind::Call(
+ &Expr {
+ kind: ExprKind::Path(ref path),
+ hir_id,
+ ..
+ },
+ args,
+ ) => match self.cx.qpath_res(path, hir_id) {
++ Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => {
++ if self
++ .cx
++ .typeck_results()
++ .expr_ty(e)
++ .has_significant_drop(self.cx.tcx, self.cx.param_env)
++ {
++ self.eagerness = Lazy;
++ return;
++ }
++ },
+ Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (),
+ // No need to walk the arguments here, `is_const_evaluatable` already did
+ Res::Def(..) if is_const_evaluatable(self.cx, e) => {
+ self.eagerness |= NoChange;
+ return;
+ },
+ Res::Def(_, id) => match path {
+ QPath::Resolved(_, p) => {
+ self.eagerness |=
+ fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, !args.is_empty());
+ },
+ QPath::TypeRelative(_, name) => {
+ self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, !args.is_empty());
+ },
+ QPath::LangItem(..) => self.eagerness = Lazy,
+ },
+ _ => self.eagerness = Lazy,
+ },
+ // No need to walk the arguments here, `is_const_evaluatable` already did
+ ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => {
+ self.eagerness |= NoChange;
+ return;
+ },
+ ExprKind::MethodCall(name, ..) => {
+ self.eagerness |= self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, true));
+ },
+ ExprKind::Index(_, e) => {
+ let ty = self.cx.typeck_results().expr_ty_adjusted(e);
+ if is_copy(self.cx, ty) && !ty.is_ref() {
+ self.eagerness |= NoChange;
+ } else {
+ self.eagerness = Lazy;
+ }
+ },
+
+ // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe.
+ ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (),
+ ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
+
+ ExprKind::Unary(_, e)
+ if matches!(
+ self.cx.typeck_results().expr_ty(e).kind(),
+ ty::Bool | ty::Int(_) | ty::Uint(_),
+ ) => {},
+ ExprKind::Binary(_, lhs, rhs)
+ if self.cx.typeck_results().expr_ty(lhs).is_primitive()
+ && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
+
+ // Can't be moved into a closure
+ ExprKind::Break(..)
+ | ExprKind::Continue(_)
+ | ExprKind::Ret(_)
+ | ExprKind::InlineAsm(_)
+ | ExprKind::Yield(..)
+ | ExprKind::Err => {
+ self.eagerness = ForceNoChange;
+ return;
+ },
+
+ // Memory allocation, custom operator, loop, or call to an unknown function
+ ExprKind::Box(_)
+ | ExprKind::Unary(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Call(..) => self.eagerness = Lazy,
+
+ ExprKind::ConstBlock(_)
+ | ExprKind::Array(_)
+ | ExprKind::Tup(_)
+ | ExprKind::Lit(_)
+ | ExprKind::Cast(..)
+ | ExprKind::Type(..)
+ | ExprKind::DropTemps(_)
+ | ExprKind::Let(..)
+ | ExprKind::If(..)
+ | ExprKind::Match(..)
+ | ExprKind::Closure { .. }
+ | ExprKind::Field(..)
+ | ExprKind::Path(_)
+ | ExprKind::AddrOf(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Block(Block { stmts: [], .. }, _) => (),
+
+ // Assignment might be to a local defined earlier, so don't eagerly evaluate.
+ // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
+ // TODO: Actually check if either of these are true here.
+ ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
+ }
+ walk_expr(self, e);
+ }
+ }
+
+ let mut v = V {
+ cx,
+ eagerness: EagernessSuggestion::Eager,
+ };
+ v.visit_expr(e);
+ v.eagerness
+}
+
+/// Whether the given expression should be changed to evaluate eagerly
+pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ expr_eagerness(cx, expr) == EagernessSuggestion::Eager
+}
+
+/// Whether the given expression should be changed to evaluate lazily
+pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
+}
--- /dev/null
- self.hash_lifetime(*lifetime);
+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,
+ PathSegment, 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.
+ let (left, right) = match (
+ snippet_opt(self.inner.cx, left.span),
+ snippet_opt(self.inner.cx, right.span),
+ ) {
+ (Some(left), Some(right)) => (left, right),
+ _ => 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::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
+ (&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => {
+ 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)
+ },
+ (&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::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
+ },
+ (&PatKind::Path(ref l), &PatKind::Path(ref 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::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)) => {
+ l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(l_rmut.ty, r_rmut.ty)
+ },
+ (&TyKind::Path(ref l), &TyKind::Path(ref 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_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 hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(e);
+ h.finish()
+}
--- /dev/null
- extern crate rustc_hir_analysis;
+#![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_analysis;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_parse_format;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
- use rustc_hir::def::{DefKind, Res};
+
+#[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 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::{
+ both, count_eq, eq_expr_value, hash_expr, hash_stmt, 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;
+use rustc_hir as hir;
- use crate::visitors::expr_visitor_no_bodies;
++use rustc_hir::def::{DefKind, Namespace, Res};
+use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
+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::{
+ def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness, Destination, Expr,
+ ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource,
+ Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind,
+ TraitRef, TyKind, UnOp,
+};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::place::PlaceBase;
+use rustc_middle::ty as rustc_ty;
+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::source_map::original_sp;
+use rustc_span::source_map::SourceMap;
+use rustc_span::sym;
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+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};
- sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
++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 {
- /// Checks if a `QPath` resolves to a constructor of a `LangItem`.
++ 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,
+ }
+}
+
- pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem) -> bool {
++/// 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(..)`.
- if let Ok(item_id) = cx.tcx.lang_items().require(lang_item) {
- return cx.tcx.parent(ctor_id) == item_id;
- }
++pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool {
++ if let Res::Def(DefKind::Ctor(..), id) = res
++ && let Ok(lang_id) = cx.tcx.lang_items().require(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 {
- /// Resolves a def path like `std::vec::Vec`.
++ 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().require(item).map_or(false, |id| id == 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 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()
+}
+
- pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
- fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str) -> Option<Res> {
++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),
++ // FIXME: rustdoc documents these two using just `pointer`.
++ //
++ // Maybe this is something we should do here too.
++ "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.
- .find(|item| item.ident.name.as_str() == name)
++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()
- 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),
- // FIXME: rustdoc documents these two using just `pointer`.
- //
- // Maybe this is something we should do here too.
- "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(),
- }
- }
++ .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,
+ }
+ }
- let (base, first, path) = match *path {
- [base, first, ref path @ ..] => (base, first, path),
++
+ fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
+ tcx.crates(())
+ .iter()
+ .copied()
+ .find(|&num| tcx.crate_name(num).as_str() == name)
+ .map(CrateNum::as_def_id)
+ }
+
- .filter_map(|id| item_child_by_name(tcx, id, first));
++ let (base, path) = match *path {
+ [primitive] => {
+ return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
+ },
++ [base, ref path @ ..] => (base, path),
+ _ => return Res::Err,
+ };
+ let tcx = cx.tcx;
+ let starts = find_primitive(tcx, base)
+ .chain(find_crate(tcx, base))
- .try_fold(first, |res, segment| {
++ .map(|id| Res::Def(tcx.def_kind(id), id));
+
+ for first in starts {
+ let last = path
+ .iter()
+ .copied()
++ .enumerate()
+ // for each segment, find the child item
- if let Some(item) = item_child_by_name(tcx, def_id, segment) {
++ .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();
- .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment))
++ 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()
- match def_path_res(cx, path) {
++ .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment, matches_ns))
+ } else {
+ None
+ }
+ });
+
+ if let Some(last) = last {
+ return last;
+ }
+ }
+
+ Res::Err
+}
+
+/// 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> {
- ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone),
++ match def_path_res(cx, path, Some(Namespace::TypeNS)) {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ _ => None,
+ }
+}
+
+/// 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::String,
+ 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() {
+ return std_types_symbols
+ .iter()
+ .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did()));
+ }
+ }
+ }
+ }
+ 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),
- let mut found = false;
- expr_visitor_no_bodies(|expr| {
- if !found {
- if let hir::ExprKind::Ret(..) = &expr.kind {
- found = true;
- }
++ 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,
+ }
+}
+
+/// 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).
+pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
+ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
+ let mut capture = CaptureKind::Ref(Mutability::Not);
+ pat.each_binding_or_first(&mut |_, id, span, _| match cx
+ .typeck_results()
+ .extract_binding_mode(cx.sess(), id, span)
+ .unwrap()
+ {
+ BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
+ capture = CaptureKind::Value;
+ },
+ BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
+ capture = CaptureKind::Ref(Mutability::Mut);
+ },
+ _ => (),
+ });
+ capture
+ }
+
+ debug_assert!(matches!(
+ e.kind,
+ ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
+ ));
+
+ let 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 {
- !found
++ for_each_expr(expr, |e| {
++ if matches!(e.kind, hir::ExprKind::Ret(..)) {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(())
+ }
- .visit_expr(expr);
- found
+ })
- /// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_hir_analysis::check::coercion` for more
- /// information on adjustments and coercions.
++ .is_some()
+}
+
+/// 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)
+}
+
+/// 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>.
+///
- if is_lang_ctor(cx, path, ResultOk);
++/// 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();
- is_lang_ctor(cx, path, ResultErr)
++ 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 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);
+/// ```
+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
+}
+
+/// 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.
+pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, 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 {
+ matches!(kind, FnKind::ItemFn(_, _, header) if header.asyncness == IsAsync::Async)
+}
+
+/// 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.def_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
- use crate::visitors::expr_visitor_no_bodies;
+#![allow(clippy::similar_names)] // `expr` and `expn`
+
+use crate::is_path_diagnostic_item;
+use crate::source::snippet_opt;
- expr_visitor_no_bodies(|e| {
++use crate::visitors::{for_each_expr, Descend};
+
+use arrayvec::ArrayVec;
+use itertools::{izip, Either, Itertools};
+use rustc_ast::ast::LitKind;
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, Node, QPath};
+use rustc_lexer::unescape::unescape_literal;
+use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
+use rustc_lint::LateContext;
+use rustc_parse_format::{self as rpf, Alignment};
+use rustc_span::def_id::DefId;
+use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
+use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
++use std::iter::{once, zip};
+use std::ops::ControlFlow;
+
+const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
+ sym::assert_eq_macro,
+ sym::assert_macro,
+ sym::assert_ne_macro,
+ sym::debug_assert_eq_macro,
+ sym::debug_assert_macro,
+ sym::debug_assert_ne_macro,
+ sym::eprint_macro,
+ sym::eprintln_macro,
+ sym::format_args_macro,
+ sym::format_macro,
+ sym::print_macro,
+ sym::println_macro,
+ sym::std_panic_macro,
+ sym::write_macro,
+ sym::writeln_macro,
+];
+
+/// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
+pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
+ if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
+ FORMAT_MACRO_DIAG_ITEMS.contains(&name)
+ } else {
+ false
+ }
+}
+
+/// A macro call, like `vec![1, 2, 3]`.
+///
+/// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
+/// Even better is to check if it is a diagnostic item.
+///
+/// This structure is similar to `ExpnData` but it precludes desugaring expansions.
+#[derive(Debug)]
+pub struct MacroCall {
+ /// Macro `DefId`
+ pub def_id: DefId,
+ /// Kind of macro
+ pub kind: MacroKind,
+ /// The expansion produced by the macro call
+ pub expn: ExpnId,
+ /// Span of the macro call site
+ pub span: Span,
+}
+
+impl MacroCall {
+ pub fn is_local(&self) -> bool {
+ span_is_local(self.span)
+ }
+}
+
+/// Returns an iterator of expansions that created the given span
+pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
+ std::iter::from_fn(move || {
+ let ctxt = span.ctxt();
+ if ctxt == SyntaxContext::root() {
+ return None;
+ }
+ let expn = ctxt.outer_expn();
+ let data = expn.expn_data();
+ span = data.call_site;
+ Some((expn, data))
+ })
+}
+
+/// Checks whether the span is from the root expansion or a locally defined macro
+pub fn span_is_local(span: Span) -> bool {
+ !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
+}
+
+/// Checks whether the expansion is the root expansion or a locally defined macro
+pub fn expn_is_local(expn: ExpnId) -> bool {
+ if expn == ExpnId::root() {
+ return true;
+ }
+ let data = expn.expn_data();
+ let backtrace = expn_backtrace(data.call_site);
+ std::iter::once((expn, data))
+ .chain(backtrace)
+ .find_map(|(_, data)| data.macro_def_id)
+ .map_or(true, DefId::is_local)
+}
+
+/// Returns an iterator of macro expansions that created the given span.
+/// Note that desugaring expansions are skipped.
+pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
+ expn_backtrace(span).filter_map(|(expn, data)| match data {
+ ExpnData {
+ kind: ExpnKind::Macro(kind, _),
+ macro_def_id: Some(def_id),
+ call_site: span,
+ ..
+ } => Some(MacroCall {
+ def_id,
+ kind,
+ expn,
+ span,
+ }),
+ _ => None,
+ })
+}
+
+/// If the macro backtrace of `span` has a macro call at the root expansion
+/// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
+pub fn root_macro_call(span: Span) -> Option<MacroCall> {
+ macro_backtrace(span).last()
+}
+
+/// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
+/// produced by the macro call, as in [`first_node_in_macro`].
+pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
+ if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
+ return None;
+ }
+ root_macro_call(node.span())
+}
+
+/// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
+/// macro call, as in [`first_node_in_macro`].
+pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
+ let span = node.span();
+ first_node_in_macro(cx, node)
+ .into_iter()
+ .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
+}
+
+/// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
+/// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
+/// is the outermost node of an entire macro expansion, but there are some caveats noted below.
+/// This is useful for finding macro calls while visiting the HIR without processing the macro call
+/// at every node within its expansion.
+///
+/// If you already have immediate access to the parent node, it is simpler to
+/// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
+///
+/// If a macro call is in statement position, it expands to one or more statements.
+/// In that case, each statement *and* their immediate descendants will all yield `Some`
+/// with the `ExpnId` of the containing block.
+///
+/// A node may be the "first node" of multiple macro calls in a macro backtrace.
+/// The expansion of the outermost macro call site is returned in such cases.
+pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
+ // get the macro expansion or return `None` if not found
+ // `macro_backtrace` importantly ignores desugaring expansions
+ let expn = macro_backtrace(node.span()).next()?.expn;
+
+ // get the parent node, possibly skipping over a statement
+ // if the parent is not found, it is sensible to return `Some(root)`
+ let hir = cx.tcx.hir();
+ let mut parent_iter = hir.parent_iter(node.hir_id());
+ let (parent_id, _) = match parent_iter.next() {
+ None => return Some(ExpnId::root()),
+ Some((_, Node::Stmt(_))) => match parent_iter.next() {
+ None => return Some(ExpnId::root()),
+ Some(next) => next,
+ },
+ Some(next) => next,
+ };
+
+ // get the macro expansion of the parent node
+ let parent_span = hir.span(parent_id);
+ let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
+ // the parent node is not in a macro
+ return Some(ExpnId::root());
+ };
+
+ if parent_macro_call.expn.is_descendant_of(expn) {
+ // `node` is input to a macro call
+ return None;
+ }
+
+ Some(parent_macro_call.expn)
+}
+
+/* Specific Macro Utils */
+
+/// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
+pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
+ matches!(
+ name.as_str(),
+ "core_panic_macro"
+ | "std_panic_macro"
+ | "core_panic_2015_macro"
+ | "std_panic_2015_macro"
+ | "core_panic_2021_macro"
+ )
+}
+
+pub enum PanicExpn<'a> {
+ /// No arguments - `panic!()`
+ Empty,
+ /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
+ Str(&'a Expr<'a>),
+ /// A single argument that implements `Display` - `panic!("{}", object)`
+ Display(&'a Expr<'a>),
+ /// Anything else - `panic!("error {}: {}", a, b)`
+ Format(FormatArgsExpn<'a>),
+}
+
+impl<'a> PanicExpn<'a> {
+ pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
+ if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
+ return None;
+ }
+ let ExprKind::Call(callee, [arg]) = &expr.kind else { return None };
+ let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
+ let result = match path.segments.last().unwrap().ident.as_str() {
+ "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
+ "panic" | "panic_str" => Self::Str(arg),
+ "panic_display" => {
+ let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
+ Self::Display(e)
+ },
+ "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
+ _ => return None,
+ };
+ Some(result)
+ }
+}
+
+/// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
+pub fn find_assert_args<'a>(
+ cx: &LateContext<'_>,
+ expr: &'a Expr<'a>,
+ expn: ExpnId,
+) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
+ find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
+}
+
+/// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
+/// expansion
+pub fn find_assert_eq_args<'a>(
+ cx: &LateContext<'_>,
+ expr: &'a Expr<'a>,
+ expn: ExpnId,
+) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
+ find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
+}
+
+fn find_assert_args_inner<'a, const N: usize>(
+ cx: &LateContext<'_>,
+ expr: &'a Expr<'a>,
+ expn: ExpnId,
+) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
+ let macro_id = expn.expn_data().macro_def_id?;
+ let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
+ None => (expr, expn),
+ Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
+ };
+ let mut args = ArrayVec::new();
+ let mut panic_expn = None;
- panic_expn.is_none()
++ let _: Option<!> = for_each_expr(expr, |e| {
+ if args.is_full() {
+ if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
+ panic_expn = PanicExpn::parse(cx, e);
+ }
- false
++ ControlFlow::Continue(Descend::from(panic_expn.is_none()))
+ } else if is_assert_arg(cx, e, expn) {
+ args.push(e);
- true
++ ControlFlow::Continue(Descend::No)
+ } else {
- })
- .visit_expr(expr);
++ ControlFlow::Continue(Descend::Yes)
+ }
- let mut found = None;
- expr_visitor_no_bodies(|e| {
- if found.is_some() || !e.span.from_expansion() {
- return false;
++ });
+ let args = args.into_inner().ok()?;
+ // if no `panic!(..)` is found, use `PanicExpn::Empty`
+ // to indicate that the default assertion message is used
+ let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
+ Some((args, panic_expn))
+}
+
+fn find_assert_within_debug_assert<'a>(
+ cx: &LateContext<'_>,
+ expr: &'a Expr<'a>,
+ expn: ExpnId,
+ assert_name: Symbol,
+) -> Option<(&'a Expr<'a>, ExpnId)> {
- return true;
- }
- if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
- found = Some((e, e_expn));
++ for_each_expr(expr, |e| {
++ if !e.span.from_expansion() {
++ return ControlFlow::Continue(Descend::No);
+ }
+ let e_expn = e.span.ctxt().outer_expn();
+ if e_expn == expn {
- false
++ ControlFlow::Continue(Descend::Yes)
++ } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
++ ControlFlow::Break((e, e_expn))
++ } else {
++ ControlFlow::Continue(Descend::No)
+ }
- .visit_expr(expr);
- found
+ })
- Err(e) => panic!("{:?}", e),
+}
+
+fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
+ if !expr.span.from_expansion() {
+ return true;
+ }
+ let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
+ if macro_call.expn == assert_expn {
+ ControlFlow::Break(false)
+ } else {
+ match cx.tcx.item_name(macro_call.def_id) {
+ // `cfg!(debug_assertions)` in `debug_assert!`
+ sym::cfg => ControlFlow::CONTINUE,
+ // assert!(other_macro!(..))
+ _ => ControlFlow::Break(true),
+ }
+ }
+ });
+ match result {
+ ControlFlow::Break(is_assert_arg) => is_assert_arg,
+ ControlFlow::Continue(()) => true,
+ }
+}
+
+/// The format string doesn't exist in the HIR, so we reassemble it from source code
+#[derive(Debug)]
+pub struct FormatString {
+ /// Span of the whole format string literal, including `[r#]"`.
+ pub span: Span,
+ /// Snippet of the whole format string literal, including `[r#]"`.
+ pub snippet: String,
+ /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
+ pub style: Option<usize>,
+ /// The unescaped value of the format string, e.g. `"val – {}"` for the literal
+ /// `"val \u{2013} {}"`.
+ pub unescaped: String,
+ /// The format string split by format args like `{..}`.
+ pub parts: Vec<Symbol>,
+}
+
+impl FormatString {
+ fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
+ // format_args!(r"a {} b \", 1);
+ //
+ // expands to
+ //
+ // ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
+ // &[::core::fmt::ArgumentV1::new_display(&1)]);
+ //
+ // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
+ let span = pieces.span;
+ let snippet = snippet_opt(cx, span)?;
+
+ let (inner, style) = match tokenize(&snippet).next()?.kind {
+ TokenKind::Literal { kind, .. } => {
+ let style = match kind {
+ LiteralKind::Str { .. } => None,
+ LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
+ _ => return None,
+ };
+
+ let start = style.map_or(1, |n| 2 + n);
+ let end = snippet.len() - style.map_or(1, |n| 1 + n);
+
+ (&snippet[start..end], style)
+ },
+ _ => return None,
+ };
+
+ let mode = if style.is_some() {
+ unescape::Mode::RawStr
+ } else {
+ unescape::Mode::Str
+ };
+
+ let mut unescaped = String::with_capacity(inner.len());
+ unescape_literal(inner, mode, &mut |_, ch| match ch {
+ Ok(ch) => unescaped.push(ch),
+ Err(e) if !e.is_fatal() => (),
- expr_visitor_no_bodies(|expr| {
- if let ExprKind::Lit(lit) = &expr.kind {
- if let LitKind::Str(symbol, _) = lit.node {
- parts.push(symbol);
- }
++ Err(e) => panic!("{e:?}"),
+ });
+
+ let mut parts = Vec::new();
-
- true
- })
- .visit_expr(pieces);
++ let _: Option<!> = for_each_expr(pieces, |expr| {
++ if let ExprKind::Lit(lit) = &expr.kind
++ && let LitKind::Str(symbol, _) = lit.node
++ {
++ parts.push(symbol);
+ }
- /// See `FormatArgsExpn::value_args`
++ ControlFlow::Continue(())
++ });
+
+ Some(Self {
+ span,
+ snippet,
+ style,
+ unescaped,
+ parts,
+ })
+ }
+}
+
+struct FormatArgsValues<'tcx> {
- expr_visitor_no_bodies(|expr| {
++ /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
++ /// `format!("{x} {} {y}", 1, z + 2)`.
+ value_args: Vec<&'tcx Expr<'tcx>>,
+ /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
+ /// `value_args`
+ pos_to_value_index: Vec<usize>,
+ /// Used to check if a value is declared inline & to resolve `InnerSpan`s.
+ format_string_span: SpanData,
+}
+
+impl<'tcx> FormatArgsValues<'tcx> {
+ fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
+ let mut pos_to_value_index = Vec::new();
+ let mut value_args = Vec::new();
-
- true
++ let _: Option<!> = for_each_expr(args, |expr| {
+ if expr.span.ctxt() == args.span.ctxt() {
+ // ArgumentV1::new_<format_trait>(<val>)
+ // ArgumentV1::from_usize(<val>)
+ if let ExprKind::Call(callee, [val]) = expr.kind
+ && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
+ && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind
+ && path.segments.last().unwrap().ident.name == sym::ArgumentV1
+ {
+ let val_idx = if val.span.ctxt() == expr.span.ctxt()
+ && let ExprKind::Field(_, field) = val.kind
+ && let Ok(idx) = field.name.as_str().parse()
+ {
+ // tuple index
+ idx
+ } else {
+ // assume the value expression is passed directly
+ pos_to_value_index.len()
+ };
+
+ pos_to_value_index.push(val_idx);
+ }
-
- false
++ ControlFlow::Continue(Descend::Yes)
+ } else {
+ // assume that any expr with a differing span is a value
+ value_args.push(expr);
- })
- .visit_expr(args);
++ ControlFlow::Continue(Descend::No)
+ }
- /// A parameter with an explicit number, or an asterisk precision. e.g. `{1}`, `{0:?}`,
- /// `{:.0$}` or `{:.*}`.
++ });
+
+ Self {
+ value_args,
+ pos_to_value_index,
+ format_string_span,
+ }
+ }
+}
+
+/// The positions of a format argument's value, precision and width
+///
+/// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
+#[derive(Debug, Default, Copy, Clone)]
+struct ParamPosition {
+ /// The position stored in `rt::v1::Argument::position`.
+ value: usize,
+ /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
+ width: Option<usize>,
+ /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
+ precision: Option<usize>,
+}
+
+impl<'tcx> Visitor<'tcx> for ParamPosition {
+ fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
+ fn parse_count(expr: &Expr<'_>) -> Option<usize> {
+ // ::core::fmt::rt::v1::Count::Param(1usize),
+ if let ExprKind::Call(ctor, [val]) = expr.kind
+ && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
+ && path.segments.last()?.ident.name == sym::Param
+ && let ExprKind::Lit(lit) = &val.kind
+ && let LitKind::Int(pos, _) = lit.node
+ {
+ Some(pos as usize)
+ } else {
+ None
+ }
+ }
+
+ match field.ident.name {
+ sym::position => {
+ if let ExprKind::Lit(lit) = &field.expr.kind
+ && let LitKind::Int(pos, _) = lit.node
+ {
+ self.value = pos as usize;
+ }
+ },
+ sym::precision => {
+ self.precision = parse_count(field.expr);
+ },
+ sym::width => {
+ self.width = parse_count(field.expr);
+ },
+ _ => walk_expr(self, field.expr),
+ }
+ }
+}
+
+/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
+fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
+ if let ExprKind::AddrOf(.., array) = fmt_arg.kind
+ && let ExprKind::Array(specs) = array.kind
+ {
+ Some(specs.iter().map(|spec| {
+ let mut position = ParamPosition::default();
+ position.visit_expr(spec);
+ position
+ }))
+ } else {
+ None
+ }
+}
+
+/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
+fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
+ Span::new(
+ base.lo + BytePos::from_usize(inner.start),
+ base.lo + BytePos::from_usize(inner.end),
+ base.ctxt,
+ base.parent,
+ )
+}
+
++/// How a format parameter is used in the format string
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FormatParamKind {
+ /// An implicit parameter , such as `{}` or `{:?}`.
+ Implicit,
- Some(Self { value, kind, span })
++ /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
+ Numbered,
++ /// A parameter with an asterisk precision. e.g. `{:.*}`.
++ Starred,
+ /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
+ Named(Symbol),
+ /// An implicit named parameter, such as the `y` in `format!("{y}")`.
+ NamedInline(Symbol),
+}
+
++/// Where a format parameter is being used in the format string
++#[derive(Debug, Copy, Clone, PartialEq, Eq)]
++pub enum FormatParamUsage {
++ /// Appears as an argument, e.g. `format!("{}", foo)`
++ Argument,
++ /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
++ Width,
++ /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
++ Precision,
++}
++
+/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
+///
+/// ```
+/// let precision = 2;
+/// format!("{:.precision$}", 0.1234);
+/// ```
+///
+/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
+/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
+#[derive(Debug, Copy, Clone)]
+pub struct FormatParam<'tcx> {
+ /// The expression this parameter refers to.
+ pub value: &'tcx Expr<'tcx>,
+ /// How this parameter refers to its `value`.
+ pub kind: FormatParamKind,
++ /// Where this format param is being used - argument/width/precision
++ pub usage: FormatParamUsage,
+ /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
+ ///
+ /// ```text
+ /// format!("{}, { }, {0}, {name}", ...);
+ /// ^ ~~ ~ ~~~~
+ /// ```
+ pub span: Span,
+}
+
+impl<'tcx> FormatParam<'tcx> {
+ fn new(
+ mut kind: FormatParamKind,
++ usage: FormatParamUsage,
+ position: usize,
+ inner: rpf::InnerSpan,
+ values: &FormatArgsValues<'tcx>,
+ ) -> Option<Self> {
+ let value_index = *values.pos_to_value_index.get(position)?;
+ let value = *values.value_args.get(value_index)?;
+ let span = span_from_inner(values.format_string_span, inner);
+
+ // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
+ // into the format string
+ if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
+ kind = FormatParamKind::NamedInline(name);
+ }
+
- rpf::Count::CountIsName(name, span) => Self::Param(FormatParam::new(
++ Some(Self {
++ value,
++ kind,
++ usage,
++ span,
++ })
+ }
+}
+
+/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
+/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
+#[derive(Debug, Copy, Clone)]
+pub enum Count<'tcx> {
+ /// Specified with a literal number, stores the value.
+ Is(usize, Span),
+ /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
+ /// `FormatParamKind::Numbered`.
+ Param(FormatParam<'tcx>),
+ /// Not specified.
+ Implied,
+}
+
+impl<'tcx> Count<'tcx> {
+ fn new(
++ usage: FormatParamUsage,
+ count: rpf::Count<'_>,
+ position: Option<usize>,
+ inner: Option<rpf::InnerSpan>,
+ values: &FormatArgsValues<'tcx>,
+ ) -> Option<Self> {
+ Some(match count {
+ rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)),
- span,
++ rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
+ FormatParamKind::Named(Symbol::intern(name)),
++ usage,
+ position?,
- rpf::Count::CountIsParam(_) | rpf::Count::CountIsStar(_) => {
- Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
- },
++ inner?,
++ values,
++ )?),
++ rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
++ FormatParamKind::Numbered,
++ usage,
++ position?,
++ inner?,
++ values,
++ )?),
++ rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
++ FormatParamKind::Starred,
++ usage,
++ position?,
++ inner?,
+ values,
+ )?),
- precision: Count::new(spec.precision, positions.precision, spec.precision_span, values)?,
- width: Count::new(spec.width, positions.width, spec.width_span, values)?,
+ rpf::Count::CountImplied => Self::Implied,
+ })
+ }
+
+ pub fn is_implied(self) -> bool {
+ matches!(self, Count::Implied)
+ }
+
+ pub fn param(self) -> Option<FormatParam<'tcx>> {
+ match self {
+ Count::Param(param) => Some(param),
+ _ => None,
+ }
+ }
+}
+
+/// Specification for the formatting of an argument in the format string. See
+/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
+#[derive(Debug)]
+pub struct FormatSpec<'tcx> {
+ /// Optionally specified character to fill alignment with.
+ pub fill: Option<char>,
+ /// Optionally specified alignment.
+ pub align: Alignment,
+ /// Packed version of various flags provided, see [`rustc_parse_format::Flag`].
+ pub flags: u32,
+ /// Represents either the maximum width or the integer precision.
+ pub precision: Count<'tcx>,
+ /// The minimum width, will be padded according to `width`/`align`
+ pub width: Count<'tcx>,
+ /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
+ /// `{:?}`.
+ pub r#trait: Symbol,
+ pub trait_span: Option<Span>,
+}
+
+impl<'tcx> FormatSpec<'tcx> {
+ fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
+ Some(Self {
+ fill: spec.fill,
+ align: spec.align,
+ flags: spec.flags,
- // The format arguments, such as `{:?}`.
++ precision: Count::new(
++ FormatParamUsage::Precision,
++ spec.precision,
++ positions.precision,
++ spec.precision_span,
++ values,
++ )?,
++ width: Count::new(
++ FormatParamUsage::Width,
++ spec.width,
++ positions.width,
++ spec.width_span,
++ values,
++ )?,
+ r#trait: match spec.ty {
+ "" => sym::Display,
+ "?" => sym::Debug,
+ "o" => sym!(Octal),
+ "x" => sym!(LowerHex),
+ "X" => sym!(UpperHex),
+ "p" => sym::Pointer,
+ "b" => sym!(Binary),
+ "e" => sym!(LowerExp),
+ "E" => sym!(UpperExp),
+ _ => return None,
+ },
+ trait_span: spec
+ .ty_span
+ .map(|span| span_from_inner(values.format_string_span, span)),
+ })
+ }
+
+ /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
+ /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
+ pub fn is_default(&self) -> bool {
+ self.r#trait == sym::Display
+ && self.width.is_implied()
+ && self.precision.is_implied()
+ && self.align == Alignment::AlignUnknown
+ && self.flags == 0
+ }
+}
+
+/// A format argument, such as `{}`, `{foo:?}`.
+#[derive(Debug)]
+pub struct FormatArg<'tcx> {
+ /// The parameter the argument refers to.
+ pub param: FormatParam<'tcx>,
+ /// How to format `param`.
+ pub format: FormatSpec<'tcx>,
+ /// span of the whole argument, `{..}`.
+ pub span: Span,
+}
+
+/// A parsed `format_args!` expansion.
+#[derive(Debug)]
+pub struct FormatArgsExpn<'tcx> {
+ /// The format string literal.
+ pub format_string: FormatString,
- /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
++ /// The format arguments, such as `{:?}`.
+ pub args: Vec<FormatArg<'tcx>>,
+ /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
+ /// include this added newline.
+ pub newline: bool,
- value_args: Vec<&'tcx Expr<'tcx>>,
++ /// Spans of the commas between the format string and explicit values, excluding any trailing
++ /// comma
++ ///
++ /// ```ignore
++ /// format!("..", 1, 2, 3,)
++ /// // ^ ^ ^
++ /// ```
++ comma_spans: Vec<Span>,
++ /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
+ /// `format!("{x} {} {y}", 1, z + 2)`.
- value_args: values.value_args,
++ explicit_values: Vec<&'tcx Expr<'tcx>>,
+}
+
+impl<'tcx> FormatArgsExpn<'tcx> {
++ /// Gets the spans of the commas inbetween the format string and explicit args, not including
++ /// any trailing comma
++ ///
++ /// ```ignore
++ /// format!("{} {}", a, b)
++ /// // ^ ^
++ /// ```
++ ///
++ /// Ensures that the format string and values aren't coming from a proc macro that sets the
++ /// output span to that of its input
++ fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
++ // `format!("{} {} {c}", "one", "two", c = "three")`
++ // ^^^^^ ^^^^^ ^^^^^^^
++ let value_spans = explicit_values
++ .iter()
++ .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
++
++ // `format!("{} {} {c}", "one", "two", c = "three")`
++ // ^^ ^^ ^^^^^^
++ let between_spans = once(fmt_span)
++ .chain(value_spans)
++ .tuple_windows()
++ .map(|(start, end)| start.between(end));
++
++ let mut comma_spans = Vec::new();
++ for between_span in between_spans {
++ let mut offset = 0;
++ let mut seen_comma = false;
++
++ for token in tokenize(&snippet_opt(cx, between_span)?) {
++ match token.kind {
++ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
++ TokenKind::Comma if !seen_comma => {
++ seen_comma = true;
++
++ let base = between_span.data();
++ comma_spans.push(Span::new(
++ base.lo + BytePos(offset),
++ base.lo + BytePos(offset + 1),
++ base.ctxt,
++ base.parent,
++ ));
++ },
++ // named arguments, `start_val, name = end_val`
++ // ^^^^^^^^^ between_span
++ TokenKind::Ident | TokenKind::Eq if seen_comma => {},
++ // An unexpected token usually indicates the format string or a value came from a proc macro output
++ // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
++ // emits a string literal with the span set to that of `"input"`
++ _ => return None,
++ }
++ offset += token.len;
++ }
++
++ if !seen_comma {
++ return None;
++ }
++ }
++
++ Some(comma_spans)
++ }
++
+ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
+ let macro_name = macro_backtrace(expr.span)
+ .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
+ .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
+ let newline = macro_name == sym::format_args_nl;
+
+ // ::core::fmt::Arguments::new_v1(pieces, args)
+ // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
+ if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
+ && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
+ && is_path_diagnostic_item(cx, ty, sym::Arguments)
+ && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
+ {
+ let format_string = FormatString::new(cx, pieces)?;
+
+ let mut parser = rpf::Parser::new(
+ &format_string.unescaped,
+ format_string.style,
+ Some(format_string.snippet.clone()),
+ // `format_string.unescaped` does not contain the appended newline
+ false,
+ rpf::ParseMode::Format,
+ );
+
+ let parsed_args = parser
+ .by_ref()
+ .filter_map(|piece| match piece {
+ rpf::Piece::NextArgument(a) => Some(a),
+ rpf::Piece::String(_) => None,
+ })
+ .collect_vec();
+ if !parser.errors.is_empty() {
+ return None;
+ }
+
+ let positions = if let Some(fmt_arg) = rest.first() {
+ // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
+ // them.
+
+ Either::Left(parse_rt_fmt(fmt_arg)?)
+ } else {
+ // If no format specs are given, the positions are in the given order and there are
+ // no `precision`/`width`s to consider.
+
+ Either::Right((0..).map(|n| ParamPosition {
+ value: n,
+ width: None,
+ precision: None,
+ }))
+ };
+
+ let values = FormatArgsValues::new(args, format_string.span.data());
+
+ let args = izip!(positions, parsed_args, parser.arg_places)
+ .map(|(position, parsed_arg, arg_span)| {
+ Some(FormatArg {
+ param: FormatParam::new(
+ match parsed_arg.position {
+ rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
+ rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
+ // NamedInline is handled by `FormatParam::new()`
+ rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
+ },
++ FormatParamUsage::Argument,
+ position.value,
+ parsed_arg.position_span,
+ &values,
+ )?,
+ format: FormatSpec::new(parsed_arg.format, position, &values)?,
+ span: span_from_inner(values.format_string_span, arg_span),
+ })
+ })
+ .collect::<Option<Vec<_>>>()?;
+
++ let mut explicit_values = values.value_args;
++ // remove values generated for implicitly captured vars
++ let len = explicit_values
++ .iter()
++ .take_while(|val| !format_string.span.contains(val.span))
++ .count();
++ explicit_values.truncate(len);
++
++ let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
++
+ Some(Self {
+ format_string,
+ args,
- let mut format_args = None;
- expr_visitor_no_bodies(|e| {
- if format_args.is_some() {
- return false;
- }
+ newline,
++ comma_spans,
++ explicit_values,
+ })
+ } else {
+ None
+ }
+ }
+
+ pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
- return true;
- }
- if e_ctxt.outer_expn().is_descendant_of(expn_id) {
- format_args = FormatArgsExpn::parse(cx, e);
++ for_each_expr(expr, |e| {
+ let e_ctxt = e.span.ctxt();
+ if e_ctxt == expr.span.ctxt() {
- false
++ ControlFlow::Continue(Descend::Yes)
++ } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
++ if let Some(args) = FormatArgsExpn::parse(cx, e) {
++ ControlFlow::Break(args)
++ } else {
++ ControlFlow::Continue(Descend::No)
++ }
++ } else {
++ ControlFlow::Continue(Descend::No)
+ }
- .visit_expr(expr);
- format_args
+ })
- match *self.value_args {
+ }
+
+ /// Source callsite span of all inputs
+ pub fn inputs_span(&self) -> Span {
++ match *self.explicit_values {
+ [] => self.format_string.span,
+ [.., last] => self
+ .format_string
+ .span
+ .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
+ }
+ }
+
++ /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
++ ///
++ /// ```ignore
++ /// format("{}.{}", 10, 11)
++ /// // ^^^^
++ /// ```
++ pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
++ for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
++ if value.hir_id == value_id {
++ return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
++ }
++ }
++
++ None
++ }
++
+ /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
+ pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
+ self.args
+ .iter()
+ .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
+ .flatten()
+ }
+}
+
+/// A node with a `HirId` and a `Span`
+pub trait HirNode {
+ fn hir_id(&self) -> HirId;
+ fn span(&self) -> Span;
+}
+
+macro_rules! impl_hir_node {
+ ($($t:ident),*) => {
+ $(impl HirNode for hir::$t<'_> {
+ fn hir_id(&self) -> HirId {
+ self.hir_id
+ }
+ fn span(&self) -> Span {
+ self.span
+ }
+ })*
+ };
+}
+
+impl_hir_node!(Expr, Pat);
+
+impl HirNode for hir::Item<'_> {
+ fn hir_id(&self) -> HirId {
+ self.hir_id()
+ }
+
+ fn span(&self) -> Span {
+ self.span
+ }
+}
--- /dev/null
- 1,50,0 { BOOL_THEN }
+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,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,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
++ 1,50,0 { BOOL_THEN, CLAMP }
+ 1,47,0 { TAU }
+ 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 }
+}
--- /dev/null
- pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
+//! 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 ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
+pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
+pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
+pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
+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 COW: [&str; 3] = ["alloc", "borrow", "Cow"];
+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 CORE_ITER_INTO_ITER: [&str; 6] = ["core", "iter", "traits", "collect", "IntoIterator", "into_iter"];
+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"];
+/// Preferably use the diagnostic item `sym::deref_method` where possible
+pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
- pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
- pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
+pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
+#[cfg(feature = "internal")]
+pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+#[cfg(feature = "internal")]
+pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
+pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
+pub const FILE: [&str; 3] = ["std", "fs", "File"];
+pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
+pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
+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"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
+#[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_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
+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 INDEX: [&str; 3] = ["core", "ops", "Index"];
+pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
+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 ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
+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 MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
+pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
+/// Preferably use the diagnostic item `sym::Option` where possible
+pub const OPTION: [&str; 3] = ["core", "option", "Option"];
+pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
+pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
+pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
+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 POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
+pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
+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"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
+#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+/// Preferably use the diagnostic item `sym::Result` where possible
+pub const RESULT: [&str; 3] = ["core", "result", "Result"];
+pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
+pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+#[cfg(feature = "internal")]
+pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
+pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
+pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
+pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
+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 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
- use crate::visitors::expr_visitor_no_bodies;
+use crate::source::snippet;
- use rustc_hir::intravisit::Visitor;
++use crate::visitors::{for_each_expr, Descend};
+use crate::{path_to_local_id, strip_pat_refs};
- let mut abort = false;
++use core::ops::ControlFlow;
+use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
+use rustc_lint::LateContext;
+use rustc_span::Span;
+use std::borrow::Cow;
+
+pub fn get_spans(
+ cx: &LateContext<'_>,
+ opt_body_id: Option<BodyId>,
+ idx: usize,
+ replacements: &[(&'static str, &'static str)],
+) -> Option<Vec<(Span, Cow<'static, str>)>> {
+ if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
+ if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind {
+ extract_clone_suggestions(cx, binding_id, replacements, body)
+ } else {
+ Some(vec![])
+ }
+ } else {
+ Some(vec![])
+ }
+}
+
+fn extract_clone_suggestions<'tcx>(
+ cx: &LateContext<'tcx>,
+ id: HirId,
+ replace: &[(&'static str, &'static str)],
+ body: &'tcx Body<'_>,
+) -> Option<Vec<(Span, Cow<'static, str>)>> {
- expr_visitor_no_bodies(|expr| {
- if abort {
- return false;
- }
- if let ExprKind::MethodCall(seg, recv, [], _) = expr.kind {
- if path_to_local_id(recv, id) {
- if seg.ident.name.as_str() == "capacity" {
- abort = true;
- return false;
- }
- for &(fn_name, suffix) in replace {
- if seg.ident.name.as_str() == fn_name {
- spans.push((expr.span, snippet(cx, recv.span, "_") + suffix));
- return false;
- }
+ let mut spans = Vec::new();
- !abort
++ for_each_expr(body, |e| {
++ if let ExprKind::MethodCall(seg, recv, [], _) = e.kind
++ && path_to_local_id(recv, id)
++ {
++ if seg.ident.as_str() == "capacity" {
++ return ControlFlow::Break(());
++ }
++ for &(fn_name, suffix) in replace {
++ if seg.ident.as_str() == fn_name {
++ spans.push((e.span, snippet(cx, recv.span, "_") + suffix));
++ return ControlFlow::Continue(Descend::No);
+ }
+ }
+ }
- .visit_body(body);
- if abort { None } else { Some(spans) }
++ ControlFlow::Continue(Descend::Yes)
+ })
++ .is_none()
++ .then_some(spans)
+}
--- /dev/null
- 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),
+// 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>)>;
+
+pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a 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,
- but `{:?}` is not stable as `const fn`",
- func,
++ 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::Misc
+ | 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(())
+}
+
+fn check_terminator<'a, 'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &'a 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`, \
- // 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.
++ 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.
+
- RustcVersion::parse(since.as_str())
- .unwrap_or_else(|err| panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{since}`, {err:?}")),
++ // 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
- Cow::Owned(format!("{}{}", code, string))
+//! Utils for extracting, inspecting or transforming source code
+
+#![allow(clippy::module_name_repetitions)]
+
+use crate::line_span;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_span::hygiene;
+use rustc_span::source_map::SourceMap;
+use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext};
+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))
++ Cow::Owned(format!("{code}{string}"))
+ } else if string.is_empty() {
- Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
++ Cow::Owned(format!("{{ {code} }}"))
+ } else {
- println!("result: {:?}", result);
++ 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))
+ })
+}
+
+/// 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
- use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+//! Contains utility functions to generate suggestions.
+#![deny(clippy::missing_docs_in_private_items)]
+
+use crate::source::{snippet, snippet_opt, snippet_with_applicability, 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_analysis::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};
- Sugg::NonParen(Cow::Owned(format!("return {}", self)))
+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_applicability(cx, expr.span, 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::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!("{{ {} }}", self)))
++ 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!("async {}", self)))
++ 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(format!("({})", sugg).into())
++ 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::NonParen(format!("({sugg})").into())
+ }
+ },
+ Sugg::BinOp(op, lhs, rhs) => {
+ let sugg = binop_to_string(op, &lhs, &rhs);
- "{} {} {}",
- lhs,
- op.to_ast_binop().expect("Those are AST ops").to_string(),
- 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!(
- AssocOp::Assign => format!("{} = {}", lhs, rhs),
++ "{lhs} {} {rhs}",
++ op.to_ast_binop().expect("Those are AST ops").to_string()
+ )
+ },
- format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs)
++ AssocOp::Assign => format!("{lhs} = {rhs}"),
+ AssocOp::AssignOp(op) => {
- AssocOp::As => format!("{} as {}", lhs, rhs),
- AssocOp::DotDot => format!("{}..{}", lhs, rhs),
- AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
- AssocOp::Colon => format!("{}: {}", lhs, rhs),
++ format!("{lhs} {}= {rhs}", token_kind_to_string(&token::BinOp(op)))
+ },
- Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
++ 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> {
- self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
++ 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());
+
- format!("{}\n", l)
++ 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!("{}{}\n", indent, l)
++ format!("{l}\n")
+ } else {
- self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
++ format!("{indent}{l}\n")
+ }
+ })
+ .collect::<String>();
+
- let sugg = format!("{}{}", self.suggestion_start, end_snip);
++ 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 hi = cx.sess().source_map().next_point(remove_span).hi();
+ let fmpos = cx.sess().source_map().lookup_byte_offset(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<'tcx>(cx: &LateContext<'_>, closure: &'tcx 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);
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ 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 _ = write!(self.suggestion_start, "{}&{}", start_snip, ident_str);
++ 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_with_proj);
++ 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 => {
- format!("{}{}", start_snip, ident)
++ 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_str)
++ format!("{start_snip}{ident}")
+ } else {
- replacement_str = format!("*{}", replacement_str);
++ 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 {
- let _ = write!(self.suggestion_start, "{}{}", start_snip, replacement_str);
++ replacement_str = format!("*{replacement_str}");
+ }
+ }
+ }
+ }
+
- fn fake_read(&mut self, _: &rustc_hir_analysis::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
++ 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,
++ _: &rustc_hir_analysis::expr_use_visitor::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
- use rustc_middle::ty::{GenericArg, GenericArgKind};
+//! 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::{
+ self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy,
+ Region, RegionKind, 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, DUMMY_SP};
+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.at(DUMMY_SP), 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,
+ })
+}
+
+/// 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());
+ tcx.infer_ctxt().enter(|infcx| {
+ 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 result = cx.tcx.infer_ctxt().enter(|infcx| {
+ let cause = rustc_middle::traits::ObligationCause::dummy();
+ if infcx.at(&cause, param_env).normalize(ty).is_ok() {
+ match ty.kind() {
+ ty::Adt(def, substs) => def.variants().iter().all(|variant| {
+ 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()
+ .require(lang_item)
+ .map_or(false, |li| li == 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)
+ .transpose_iter()
+ .map(|x| x.map_bound(|(p, _)| p))
+ {
+ match pred.0.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
+ .map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1)))
+ .subst(cx.tcx, ty.substs);
+
+ 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 = Some(
+ pred.map_bound(|pred| pred.kind().rebind(p.term.ty().unwrap()))
+ .subst(cx.tcx, ty.substs),
+ );
+ },
+ _ => (),
+ }
+ }
+
+ 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 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 })
+}
+
+/// 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,
+ }
+}
--- /dev/null
- use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
+use crate as utils;
- use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
++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_analysis::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;
- fn fake_read(&mut self, _: &rustc_hir_analysis::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+
+/// 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,
+ };
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ 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);
+ }
+
- let mut seen_return_break_continue = false;
- expr_visitor_no_bodies(|ex| {
- if seen_return_break_continue {
- return false;
- }
- match &ex.kind {
- ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
- seen_return_break_continue = true;
- },
++ fn fake_read(
++ &mut self,
++ _: &rustc_hir_analysis::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 {
- _ => {
- if ex.span.from_expansion() {
- seen_return_break_continue = true;
- }
- },
++ 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.
- !seen_return_break_continue
++ _ if e.span.from_expansion() => ControlFlow::Break(()),
++ _ => ControlFlow::Continue(()),
+ }
- .visit_expr(expression);
- seen_return_break_continue
+ })
- let mut used_after_expr = false;
++ .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;
+ }
+
- expr_visitor(cx, |expr| {
- if used_after_expr {
- return false;
- }
-
- if expr.hir_id == after.hir_id {
+ let mut past_expr = false;
- return false;
- }
-
- if past_expr && utils::path_to_local_id(expr, local_id) {
- used_after_expr = true;
++ for_each_expr_with_closures(cx, block, |e| {
++ if e.hir_id == after.hir_id {
+ past_expr = true;
- !used_after_expr
++ ControlFlow::Continue(Descend::No)
++ } else if past_expr && utils::path_to_local_id(e, local_id) {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(Descend::Yes)
+ }
- .visit_block(block);
- used_after_expr
+ })
++ .is_some()
+}
--- /dev/null
- Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp,
- UnsafeSource, Unsafety,
+use crate::ty::needs_ordered_drop;
+use crate::{get_enclosing_block, path_to_local_id};
+use core::ops::ControlFlow;
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
+use rustc_hir::{
- use rustc_middle::hir::map::Map;
++ AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath,
++ Stmt, UnOp, UnsafeSource, Unsafety,
+};
+use rustc_lint::LateContext;
- use rustc_middle::ty::{self, Ty, TypeckResults};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::adjustment::Adjust;
- /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
- /// bodies (i.e. closures) are visited.
- /// If the callback returns `true`, the expr just provided to the callback is walked.
- #[must_use]
- pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
- struct V<'tcx, F> {
- hir: Map<'tcx>,
++use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
+use rustc_span::Span;
+
+mod internal {
+ /// Trait for visitor functions to control whether or not to descend to child nodes. Implemented
+ /// for only two types. `()` always descends. `Descend` allows controlled descent.
+ pub trait Continue {
+ fn descend(&self) -> bool;
+ }
+}
+use internal::Continue;
+
+impl Continue for () {
+ fn descend(&self) -> bool {
+ true
+ }
+}
+
+/// Allows for controlled descent when using visitor functions. Use `()` instead when always
+/// descending into child nodes.
+#[derive(Clone, Copy)]
+pub enum Descend {
+ Yes,
+ No,
+}
+impl From<bool> for Descend {
+ fn from(from: bool) -> Self {
+ if from { Self::Yes } else { Self::No }
+ }
+}
+impl Continue for Descend {
+ fn descend(&self) -> bool {
+ matches!(self, Self::Yes)
+ }
+}
+
++/// A type which can be visited.
++pub trait Visitable<'tcx> {
++ /// Calls the corresponding `visit_*` function on the visitor.
++ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
++}
++macro_rules! visitable_ref {
++ ($t:ident, $f:ident) => {
++ impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
++ visitor.$f(self);
++ }
++ }
++ };
++}
++visitable_ref!(Arm, visit_arm);
++visitable_ref!(Block, visit_block);
++visitable_ref!(Body, visit_body);
++visitable_ref!(Expr, visit_expr);
++visitable_ref!(Stmt, visit_stmt);
++
+/// Calls the given function once for each expression contained. This does not enter any bodies or
+/// nested items.
+pub fn for_each_expr<'tcx, B, C: Continue>(
+ node: impl Visitable<'tcx>,
+ f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
+) -> Option<B> {
+ struct V<B, F> {
+ f: F,
+ res: Option<B>,
+ }
+ impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<B, F> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
+ if self.res.is_some() {
+ return;
+ }
+ match (self.f)(e) {
+ ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
+ ControlFlow::Break(b) => self.res = Some(b),
+ ControlFlow::Continue(_) => (),
+ }
+ }
+
+ // Avoid unnecessary `walk_*` calls.
+ fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
+ fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
+ fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
+ // Avoid monomorphising all `visit_*` functions.
+ fn visit_nested_item(&mut self, _: ItemId) {}
+ }
+ let mut v = V { f, res: None };
+ node.visit(&mut v);
+ v.res
+}
+
- impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
++/// Calls the given function once for each expression contained. This will enter bodies, but not
++/// nested items.
++pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
++ cx: &LateContext<'tcx>,
++ node: impl Visitable<'tcx>,
++ f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
++) -> Option<B> {
++ struct V<'tcx, B, F> {
++ tcx: TyCtxt<'tcx>,
+ f: F,
++ res: Option<B>,
+ }
- self.hir
++ impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<'tcx, B, F> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- if (self.f)(expr) {
- walk_expr(self, expr);
++ self.tcx.hir()
+ }
+
- }
- }
- V { hir: cx.tcx.hir(), f }
- }
-
- /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
- /// bodies (i.e. closures) are not visited.
- /// If the callback returns `true`, the expr just provided to the callback is walked.
- #[must_use]
- pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
- struct V<F>(F);
- impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
- fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
- if (self.0)(e) {
- walk_expr(self, e);
++ fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
++ if self.res.is_some() {
++ return;
+ }
- V(f)
++ match (self.f)(e) {
++ ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
++ ControlFlow::Break(b) => self.res = Some(b),
++ ControlFlow::Continue(_) => (),
+ }
+ }
++
++ // Only walk closures
++ fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
++ // Avoid unnecessary `walk_*` calls.
++ fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
++ fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
++ fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
++ // Avoid monomorphising all `visit_*` functions.
++ fn visit_nested_item(&mut self, _: ItemId) {}
+ }
- let mut found = false;
- expr_visitor_no_bodies(|e| {
- if !found {
- found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
++ let mut v = V {
++ tcx: cx.tcx,
++ f,
++ res: None,
++ };
++ node.visit(&mut v);
++ v.res
+}
+
+/// returns `true` if expr contains match expr desugared from try
+fn contains_try(expr: &hir::Expr<'_>) -> bool {
- !found
++ for_each_expr(expr, |e| {
++ if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)) {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(())
+ }
- .visit_expr(expr);
- found
+ })
- /// A type which can be visited.
- pub trait Visitable<'tcx> {
- /// Calls the corresponding `visit_*` function on the visitor.
- fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
- }
- macro_rules! visitable_ref {
- ($t:ident, $f:ident) => {
- impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
- fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
- visitor.$f(self);
- }
- }
- };
- }
- visitable_ref!(Arm, visit_arm);
- visitable_ref!(Block, visit_block);
- visitable_ref!(Body, visit_body);
- visitable_ref!(Expr, visit_expr);
- visitable_ref!(Stmt, visit_stmt);
-
- // impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
- // where
- // I::Item: Visitable<'tcx>,
- // {
- // fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
- // for x in self {
- // x.visit(visitor);
- // }
- // }
- // }
-
++ .is_some()
+}
+
+pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
+where
+ F: FnMut(&'hir hir::Expr<'hir>) -> bool,
+{
+ struct RetFinder<F> {
+ in_stmt: bool,
+ failed: bool,
+ cb: F,
+ }
+
+ struct WithStmtGuarg<'a, F> {
+ val: &'a mut RetFinder<F>,
+ prev_in_stmt: bool,
+ }
+
+ impl<F> RetFinder<F> {
+ fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
+ let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
+ WithStmtGuarg {
+ val: self,
+ prev_in_stmt,
+ }
+ }
+ }
+
+ impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
+ type Target = RetFinder<F>;
+
+ fn deref(&self) -> &Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> Drop for WithStmtGuarg<'_, F> {
+ fn drop(&mut self) {
+ self.val.in_stmt = self.prev_in_stmt;
+ }
+ }
+
+ impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
+ fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
+ intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt);
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
+ if self.failed {
+ return;
+ }
+ if self.in_stmt {
+ match expr.kind {
+ hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
+ _ => intravisit::walk_expr(self, expr),
+ }
+ } else {
+ match expr.kind {
+ hir::ExprKind::If(cond, then, else_opt) => {
+ self.inside_stmt(true).visit_expr(cond);
+ self.visit_expr(then);
+ if let Some(el) = else_opt {
+ self.visit_expr(el);
+ }
+ },
+ hir::ExprKind::Match(cond, arms, _) => {
+ self.inside_stmt(true).visit_expr(cond);
+ for arm in arms {
+ self.visit_expr(arm.body);
+ }
+ },
+ hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
+ hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
+ _ => self.failed |= !(self.cb)(expr),
+ }
+ }
+ }
+ }
+
+ !contains_try(expr) && {
+ let mut ret_finder = RetFinder {
+ in_stmt: false,
+ failed: false,
+ cb: callback,
+ };
+ ret_finder.visit_expr(expr);
+ !ret_finder.failed
+ }
+}
+
- let mut found = false;
- expr_visitor(cx, |e| {
- if found {
- return false;
- }
-
+/// Checks if the given resolved path is used in the given body.
+pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
- found = true;
++ for_each_expr_with_closures(cx, cx.tcx.hir().body(body).value, |e| {
+ if let ExprKind::Path(p) = &e.kind {
+ if cx.qpath_res(p, e.hir_id) == res {
- !found
++ return ControlFlow::Break(());
+ }
+ }
- .visit_expr(cx.tcx.hir().body(body).value);
- found
++ ControlFlow::Continue(())
+ })
- let mut is_used = false;
- let mut visitor = expr_visitor(cx, |expr| {
- if !is_used {
- is_used = path_to_local_id(expr, id);
++ .is_some()
+}
+
+/// Checks if the given local is used.
+pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
- !is_used
- });
- visitable.visit(&mut visitor);
- drop(visitor);
- is_used
++ for_each_expr_with_closures(cx, visitable, |e| {
++ if path_to_local_id(e, id) {
++ ControlFlow::Break(())
++ } else {
++ ControlFlow::Continue(())
+ }
++ })
++ .is_some()
+}
+
+/// Checks if the given expression is a constant.
+pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ is_const: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !self.is_const {
+ return;
+ }
+ match e.kind {
+ ExprKind::ConstBlock(_) => return,
+ ExprKind::Call(
+ &Expr {
+ kind: ExprKind::Path(ref p),
+ hir_id,
+ ..
+ },
+ _,
+ ) if self
+ .cx
+ .qpath_res(p, hir_id)
+ .opt_def_id()
+ .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
+ ExprKind::MethodCall(..)
+ if self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
+ ExprKind::Binary(_, lhs, rhs)
+ if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
+ && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
+ ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
+ ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
+ ExprKind::Index(base, _)
+ if matches!(
+ self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
+ ty::Slice(_) | ty::Array(..)
+ ) => {},
+ ExprKind::Path(ref p)
+ if matches!(
+ self.cx.qpath_res(p, e.hir_id),
+ Res::Def(
+ DefKind::Const
+ | DefKind::AssocConst
+ | DefKind::AnonConst
+ | DefKind::ConstParam
+ | DefKind::Ctor(..)
+ | DefKind::Fn
+ | DefKind::AssocFn,
+ _
+ ) | Res::SelfCtor(_)
+ ) => {},
+
+ ExprKind::AddrOf(..)
+ | ExprKind::Array(_)
+ | ExprKind::Block(..)
+ | ExprKind::Cast(..)
+ | ExprKind::DropTemps(_)
+ | ExprKind::Field(..)
+ | ExprKind::If(..)
+ | ExprKind::Let(..)
+ | ExprKind::Lit(_)
+ | ExprKind::Match(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Tup(_)
+ | ExprKind::Type(..) => (),
+
+ _ => {
+ self.is_const = false;
+ return;
+ },
+ }
+ walk_expr(self, e);
+ }
+ }
+
+ let mut v = V { cx, is_const: true };
+ v.visit_expr(e);
+ v.is_const
+}
+
+/// Checks if the given expression performs an unsafe operation outside of an unsafe block.
+pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ is_unsafe: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if self.is_unsafe {
+ return;
+ }
+ match e.kind {
+ ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => {
+ self.is_unsafe = true;
+ },
+ ExprKind::MethodCall(..)
+ if self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(e.hir_id)
+ .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) =>
+ {
+ self.is_unsafe = true;
+ },
+ ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
+ ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
+ ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
+ _ => walk_expr(self, e),
+ },
+ ExprKind::Path(ref p)
+ if self
+ .cx
+ .qpath_res(p, e.hir_id)
+ .opt_def_id()
+ .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) =>
+ {
+ self.is_unsafe = true;
+ },
+ _ => walk_expr(self, e),
+ }
+ }
+ fn visit_block(&mut self, b: &'tcx Block<'_>) {
+ if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
+ walk_block(self, b);
+ }
+ }
+ fn visit_nested_item(&mut self, id: ItemId) {
+ if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind {
+ self.is_unsafe = i.unsafety == Unsafety::Unsafe;
+ }
+ }
+ }
+ let mut v = V { cx, is_unsafe: false };
+ v.visit_expr(e);
+ v.is_unsafe
+}
+
+/// Checks if the given expression contains an unsafe block
+pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ found_unsafe: bool,
+ }
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_block(&mut self, b: &'tcx Block<'_>) {
+ if self.found_unsafe {
+ return;
+ }
+ if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
+ self.found_unsafe = true;
+ return;
+ }
+ walk_block(self, b);
+ }
+ }
+ let mut v = V {
+ cx,
+ found_unsafe: false,
+ };
+ v.visit_expr(e);
+ v.found_unsafe
+}
+
+/// Runs the given function for each sub-expression producing the final value consumed by the parent
+/// of the give expression.
+///
+/// e.g. for the following expression
+/// ```rust,ignore
+/// if foo {
+/// f(0)
+/// } else {
+/// 1 + 1
+/// }
+/// ```
+/// this will pass both `f(0)` and `1+1` to the given function.
+pub fn for_each_value_source<'tcx, B>(
+ e: &'tcx Expr<'tcx>,
+ f: &mut impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+ match e.kind {
+ ExprKind::Block(Block { expr: Some(e), .. }, _) => for_each_value_source(e, f),
+ ExprKind::Match(_, arms, _) => {
+ for arm in arms {
+ for_each_value_source(arm.body, f)?;
+ }
+ ControlFlow::Continue(())
+ },
+ ExprKind::If(_, if_expr, Some(else_expr)) => {
+ for_each_value_source(if_expr, f)?;
+ for_each_value_source(else_expr, f)
+ },
+ ExprKind::DropTemps(e) => for_each_value_source(e, f),
+ _ => f(e),
+ }
+}
+
+/// Runs the given function for each path expression referencing the given local which occur after
+/// the given expression.
+pub fn for_each_local_use_after_expr<'tcx, B>(
+ cx: &LateContext<'tcx>,
+ local_id: HirId,
+ expr_id: HirId,
+ f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+ struct V<'cx, 'tcx, F, B> {
+ cx: &'cx LateContext<'tcx>,
+ local_id: HirId,
+ expr_id: HirId,
+ found: bool,
+ res: ControlFlow<B>,
+ f: F,
+ }
+ impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
+ if !self.found {
+ if e.hir_id == self.expr_id {
+ self.found = true;
+ } else {
+ walk_expr(self, e);
+ }
+ return;
+ }
+ if self.res.is_break() {
+ return;
+ }
+ if path_to_local_id(e, self.local_id) {
+ self.res = (self.f)(e);
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ if let Some(b) = get_enclosing_block(cx, local_id) {
+ let mut v = V {
+ cx,
+ local_id,
+ expr_id,
+ found: false,
+ res: ControlFlow::Continue(()),
+ f,
+ };
+ v.visit_block(b);
+ v.res
+ } else {
+ ControlFlow::Continue(())
+ }
+}
+
+// Calls the given function for every unconsumed temporary created by the expression. Note the
+// function is only guaranteed to be called for types which need to be dropped, but it may be called
+// for other types.
+#[allow(clippy::too_many_lines)]
+pub fn for_each_unconsumed_temporary<'tcx, B>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'tcx>,
+ mut f: impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+ // Todo: Handle partially consumed values.
+ fn helper<'tcx, B>(
+ typeck: &'tcx TypeckResults<'tcx>,
+ consume: bool,
+ e: &'tcx Expr<'tcx>,
+ f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
+ ) -> ControlFlow<B> {
+ if !consume
+ || matches!(
+ typeck.expr_adjustments(e),
+ [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_))
+ )
+ {
+ match e.kind {
+ ExprKind::Path(QPath::Resolved(None, p))
+ if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) =>
+ {
+ f(typeck.expr_ty(e))?;
+ },
+ ExprKind::Path(_)
+ | ExprKind::Unary(UnOp::Deref, _)
+ | ExprKind::Index(..)
+ | ExprKind::Field(..)
+ | ExprKind::AddrOf(..) => (),
+ _ => f(typeck.expr_ty(e))?,
+ }
+ }
+ match e.kind {
+ ExprKind::AddrOf(_, _, e)
+ | ExprKind::Field(e, _)
+ | ExprKind::Unary(UnOp::Deref, e)
+ | ExprKind::Match(e, ..)
+ | ExprKind::Let(&Let { init: e, .. }) => {
+ helper(typeck, false, e, f)?;
+ },
+ ExprKind::Block(&Block { expr: Some(e), .. }, _)
+ | ExprKind::Box(e)
+ | ExprKind::Cast(e, _)
+ | ExprKind::Unary(_, e) => {
+ helper(typeck, true, e, f)?;
+ },
+ ExprKind::Call(callee, args) => {
+ helper(typeck, true, callee, f)?;
+ for arg in args {
+ helper(typeck, true, arg, f)?;
+ }
+ },
+ ExprKind::MethodCall(_, receiver, args, _) => {
+ helper(typeck, true, receiver, f)?;
+ for arg in args {
+ helper(typeck, true, arg, f)?;
+ }
+ },
+ ExprKind::Tup(args) | ExprKind::Array(args) => {
+ for arg in args {
+ helper(typeck, true, arg, f)?;
+ }
+ },
+ ExprKind::Index(borrowed, consumed)
+ | ExprKind::Assign(borrowed, consumed, _)
+ | ExprKind::AssignOp(_, borrowed, consumed) => {
+ helper(typeck, false, borrowed, f)?;
+ helper(typeck, true, consumed, f)?;
+ },
+ ExprKind::Binary(_, lhs, rhs) => {
+ helper(typeck, true, lhs, f)?;
+ helper(typeck, true, rhs, f)?;
+ },
+ ExprKind::Struct(_, fields, default) => {
+ for field in fields {
+ helper(typeck, true, field.expr, f)?;
+ }
+ if let Some(default) = default {
+ helper(typeck, false, default, f)?;
+ }
+ },
+ ExprKind::If(cond, then, else_expr) => {
+ helper(typeck, true, cond, f)?;
+ helper(typeck, true, then, f)?;
+ if let Some(else_expr) = else_expr {
+ helper(typeck, true, else_expr, f)?;
+ }
+ },
+ ExprKind::Type(e, _) => {
+ helper(typeck, consume, e, f)?;
+ },
+
+ // Either drops temporaries, jumps out of the current expression, or has no sub expression.
+ ExprKind::DropTemps(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Break(..)
+ | ExprKind::Yield(..)
+ | ExprKind::Block(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Lit(_)
+ | ExprKind::ConstBlock(_)
+ | ExprKind::Closure { .. }
+ | ExprKind::Path(_)
+ | ExprKind::Continue(_)
+ | ExprKind::InlineAsm(_)
+ | ExprKind::Err => (),
+ }
+ ControlFlow::Continue(())
+ }
+ helper(cx.typeck_results(), true, e, &mut f)
+}
+
+pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
+ for_each_unconsumed_temporary(cx, e, |ty| {
+ if needs_ordered_drop(cx, ty) {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(())
+ }
+ })
+ .is_break()
+}
+
+/// Runs the given function for each path expression referencing the given local which occur after
+/// the given expression.
+pub fn for_each_local_assignment<'tcx, B>(
+ cx: &LateContext<'tcx>,
+ local_id: HirId,
+ f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
+) -> ControlFlow<B> {
+ struct V<'cx, 'tcx, F, B> {
+ cx: &'cx LateContext<'tcx>,
+ local_id: HirId,
+ res: ControlFlow<B>,
+ f: F,
+ }
+ impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> {
+ type NestedFilter = nested_filter::OnlyBodies;
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.cx.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
+ if let ExprKind::Assign(lhs, rhs, _) = e.kind
+ && self.res.is_continue()
+ && path_to_local_id(lhs, self.local_id)
+ {
+ self.res = (self.f)(rhs);
+ self.visit_expr(rhs);
+ } else {
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ if let Some(b) = get_enclosing_block(cx, local_id) {
+ let mut v = V {
+ cx,
+ local_id,
+ res: ControlFlow::Continue(()),
+ f,
+ };
+ v.visit_block(b);
+ v.res
+ } else {
+ ControlFlow::Continue(())
+ }
+}
--- /dev/null
+[package]
+name = "lintcheck"
+version = "0.0.1"
+description = "tool to monitor impact of changes in Clippy's lints on a part of the ecosystem"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/rust-clippy"
+categories = ["development-tools"]
+edition = "2021"
+publish = false
+
+[dependencies]
+cargo_metadata = "0.14"
+clap = "3.2"
++crossbeam-channel = "0.5.6"
+flate2 = "1.0"
+rayon = "1.5.1"
+serde = { version = "1.0", features = ["derive"] }
++serde_json = "1.0.85"
+tar = "0.4"
+toml = "0.5"
+ureq = "2.2"
+walkdir = "2.3"
+
+[features]
+deny-warnings = []
--- /dev/null
- You can run `./lintcheck/target/debug/lintcheck --fix` which will run Clippy with `--fix` and
+## `cargo lintcheck`
+
+Runs clippy on a fixed set of crates read from
+`lintcheck/lintcheck_crates.toml` and saves logs of the lint warnings into the
+repo. We can then check the diff and spot new or disappearing warnings.
+
+From the repo root, run:
+
+```
+cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml
+```
+
+or
+
+```
+cargo lintcheck
+```
+
+By default the logs will be saved into
+`lintcheck-logs/lintcheck_crates_logs.txt`.
+
+You can set a custom sources.toml by adding `--crates-toml custom.toml` or using
+`LINTCHECK_TOML="custom.toml"` where `custom.toml` must be a relative path from
+the repo root.
+
+The results will then be saved to `lintcheck-logs/custom_logs.toml`.
+
+### Configuring the Crate Sources
+
+The sources to check are saved in a `toml` file. There are three types of
+sources.
+
+1. Crates-io Source
+
+ ```toml
+ bitflags = {name = "bitflags", versions = ['1.2.1']}
+ ```
+ Requires a "name" and one or multiple "versions" to be checked.
+
+2. `git` Source
+ ````toml
+ puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
+ ````
+ Requires a name, the url to the repo and unique identifier of a commit,
+ branch or tag which is checked out before linting. There is no way to always
+ check `HEAD` because that would lead to changing lint-results as the repo
+ would get updated. If `git_url` or `git_hash` is missing, an error will be
+ thrown.
+
+3. Local Dependency
+ ```toml
+ clippy = {name = "clippy", path = "/home/user/clippy"}
+ ```
+ For when you want to add a repository that is not published yet.
+
+#### Command Line Options (optional)
+
+```toml
+bitflags = {name = "bitflags", versions = ['1.2.1'], options = ['-Wclippy::pedantic', '-Wclippy::cargo']}
+```
+
+It is possible to specify command line options for each crate. This makes it
+possible to only check a crate for certain lint groups. If no options are
+specified, the lint groups `clippy::all`, `clippy::pedantic`, and
+`clippy::cargo` are checked. If an empty array is specified only `clippy::all`
+is checked.
+
+**Note:** `-Wclippy::all` is always enabled by default, unless `-Aclippy::all`
+is explicitly specified in the options.
+
+### Fix mode
++You can run `cargo lintcheck --fix` which will run Clippy with `--fix` and
+print a warning if Clippy's suggestions fail to apply (if the resulting code does not build).
+This lets us spot bad suggestions or false positives automatically in some cases.
+
+Please note that the target dir should be cleaned afterwards since clippy will modify
+the downloaded sources which can lead to unexpected results when running lintcheck again afterwards.
++
++### Recursive mode
++You can run `cargo lintcheck --recursive` to also run Clippy on the dependencies
++of the crates listed in the crates source `.toml`. e.g. adding `rand 0.8.5`
++would also lint `rand_core`, `rand_chacha`, etc.
++
++Particularly slow crates in the dependency graph can be ignored using
++`recursive.ignore`:
++
++```toml
++[crates]
++cargo = {name = "cargo", versions = ['0.64.0']}
++
++[recursive]
++ignore = [
++ "unicode-normalization",
++]
++```
--- /dev/null
+[crates]
+# some of these are from cargotest
+cargo = {name = "cargo", versions = ['0.64.0']}
+iron = {name = "iron", versions = ['0.6.1']}
+ripgrep = {name = "ripgrep", versions = ['12.1.1']}
+xsv = {name = "xsv", versions = ['0.13.0']}
+# commented out because of 173K clippy::match_same_arms msgs in language_type.rs
+#tokei = { name = "tokei", versions = ['12.0.4']}
+rayon = {name = "rayon", versions = ['1.5.0']}
+serde = {name = "serde", versions = ['1.0.118']}
+# top 10 crates.io dls
+bitflags = {name = "bitflags", versions = ['1.2.1']}
+# crash = {name = "clippy_crash", path = "/tmp/clippy_crash"}
+libc = {name = "libc", versions = ['0.2.81']}
+log = {name = "log", versions = ['0.4.11']}
+proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']}
+quote = {name = "quote", versions = ['1.0.7']}
+rand = {name = "rand", versions = ['0.7.3']}
+rand_core = {name = "rand_core", versions = ['0.6.0']}
+regex = {name = "regex", versions = ['1.3.2']}
+syn = {name = "syn", versions = ['1.0.54']}
+unicode-xid = {name = "unicode-xid", versions = ['0.2.1']}
+# some more of dtolnays crates
+anyhow = {name = "anyhow", versions = ['1.0.38']}
+async-trait = {name = "async-trait", versions = ['0.1.42']}
+cxx = {name = "cxx", versions = ['1.0.32']}
+ryu = {name = "ryu", versions = ['1.0.5']}
+serde_yaml = {name = "serde_yaml", versions = ['0.8.17']}
+thiserror = {name = "thiserror", versions = ['1.0.24']}
+# some embark crates, there are other interesting crates but
+# unfortunately adding them increases lintcheck runtime drastically
+cfg-expr = {name = "cfg-expr", versions = ['0.7.1']}
+puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
+rpmalloc = {name = "rpmalloc", versions = ['0.2.0']}
+tame-oidc = {name = "tame-oidc", versions = ['0.1.0']}
++
++[recursive]
++ignore = [
++ # Takes ~30s to lint
++ "combine",
++ # Has 1.2 million `clippy::match_same_arms`s
++ "unicode-normalization",
++]
--- /dev/null
- #[derive(Debug)]
+use clap::{Arg, ArgAction, ArgMatches, Command};
+use std::env;
+use std::path::PathBuf;
+
+fn get_clap_config() -> ArgMatches {
+ Command::new("lintcheck")
+ .about("run clippy on a set of crates and check output")
+ .args([
+ Arg::new("only")
+ .action(ArgAction::Set)
+ .value_name("CRATE")
+ .long("only")
+ .help("Only process a single crate of the list"),
+ Arg::new("crates-toml")
+ .action(ArgAction::Set)
+ .value_name("CRATES-SOURCES-TOML-PATH")
+ .long("crates-toml")
+ .help("Set the path for a crates.toml where lintcheck should read the sources from"),
+ Arg::new("threads")
+ .action(ArgAction::Set)
+ .value_name("N")
+ .value_parser(clap::value_parser!(usize))
+ .short('j')
+ .long("jobs")
+ .help("Number of threads to use, 0 automatic choice"),
+ Arg::new("fix")
+ .long("fix")
+ .help("Runs cargo clippy --fix and checks if all suggestions apply"),
+ Arg::new("filter")
+ .long("filter")
+ .action(ArgAction::Append)
+ .value_name("clippy_lint_name")
+ .help("Apply a filter to only collect specified lints, this also overrides `allow` attributes"),
+ Arg::new("markdown")
+ .long("markdown")
+ .help("Change the reports table to use markdown links"),
++ Arg::new("recursive")
++ .long("--recursive")
++ .help("Run clippy on the dependencies of crates specified in crates-toml")
++ .conflicts_with("threads")
++ .conflicts_with("fix"),
+ ])
+ .get_matches()
+}
+
++#[derive(Debug, Clone)]
+pub(crate) struct LintcheckConfig {
+ /// max number of jobs to spawn (default 1)
+ pub max_jobs: usize,
+ /// we read the sources to check from here
+ pub sources_toml_path: PathBuf,
+ /// we save the clippy lint results here
+ pub lintcheck_results_path: PathBuf,
+ /// Check only a specified package
+ pub only: Option<String>,
+ /// whether to just run --fix and not collect all the warnings
+ pub fix: bool,
+ /// A list of lints that this lintcheck run should focus on
+ pub lint_filter: Vec<String>,
+ /// Indicate if the output should support markdown syntax
+ pub markdown: bool,
++ /// Run clippy on the dependencies of crates
++ pub recursive: bool,
+}
+
+impl LintcheckConfig {
+ pub fn new() -> Self {
+ let clap_config = get_clap_config();
+
+ // first, check if we got anything passed via the LINTCHECK_TOML env var,
+ // if not, ask clap if we got any value for --crates-toml <foo>
+ // if not, use the default "lintcheck/lintcheck_crates.toml"
+ let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| {
+ clap_config
+ .get_one::<String>("crates-toml")
+ .map(|s| &**s)
+ .unwrap_or("lintcheck/lintcheck_crates.toml")
+ .into()
+ });
+
+ let markdown = clap_config.contains_id("markdown");
+ let sources_toml_path = PathBuf::from(sources_toml);
+
+ // for the path where we save the lint results, get the filename without extension (so for
+ // wasd.toml, use "wasd"...)
+ let filename: PathBuf = sources_toml_path.file_stem().unwrap().into();
+ let lintcheck_results_path = PathBuf::from(format!(
+ "lintcheck-logs/{}_logs.{}",
+ filename.display(),
+ if markdown { "md" } else { "txt" }
+ ));
+
+ // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and
+ // use half of that for the physical core count
+ // by default use a single thread
+ let max_jobs = match clap_config.get_one::<usize>("threads") {
+ Some(&0) => {
+ // automatic choice
+ // Rayon seems to return thread count so half that for core count
+ (rayon::current_num_threads() / 2) as usize
+ },
+ Some(&threads) => threads,
+ // no -j passed, use a single thread
+ None => 1,
+ };
+
+ let lint_filter: Vec<String> = clap_config
+ .get_many::<String>("filter")
+ .map(|iter| {
+ iter.map(|lint_name| {
+ let mut filter = lint_name.replace('_', "-");
+ if !filter.starts_with("clippy::") {
+ filter.insert_str(0, "clippy::");
+ }
+ filter
+ })
+ .collect()
+ })
+ .unwrap_or_default();
+
+ LintcheckConfig {
+ max_jobs,
+ sources_toml_path,
+ lintcheck_results_path,
+ only: clap_config.get_one::<String>("only").map(String::from),
+ fix: clap_config.contains_id("fix"),
+ lint_filter,
+ markdown,
++ recursive: clap_config.contains_id("recursive"),
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use crate::recursive::{deserialize_line, serialize_line, DriverInfo};
++
++use std::io::{self, BufReader, Write};
++use std::net::TcpStream;
++use std::process::{self, Command, Stdio};
++use std::{env, mem};
++
++/// 1. Sends [DriverInfo] to the [crate::recursive::LintcheckServer] running on `addr`
++/// 2. Receives [bool] from the server, if `false` returns `None`
++/// 3. Otherwise sends the stderr of running `clippy-driver` to the server
++fn run_clippy(addr: &str) -> Option<i32> {
++ let driver_info = DriverInfo {
++ package_name: env::var("CARGO_PKG_NAME").ok()?,
++ crate_name: env::var("CARGO_CRATE_NAME").ok()?,
++ version: env::var("CARGO_PKG_VERSION").ok()?,
++ };
++
++ let mut stream = BufReader::new(TcpStream::connect(addr).unwrap());
++
++ serialize_line(&driver_info, stream.get_mut());
++
++ let should_run = deserialize_line::<bool, _>(&mut stream);
++ if !should_run {
++ return None;
++ }
++
++ // Remove --cap-lints allow so that clippy runs and lints are emitted
++ let mut include_next = true;
++ let args = env::args().skip(1).filter(|arg| match arg.as_str() {
++ "--cap-lints=allow" => false,
++ "--cap-lints" => {
++ include_next = false;
++ false
++ },
++ _ => mem::replace(&mut include_next, true),
++ });
++
++ let output = Command::new(env::var("CLIPPY_DRIVER").expect("missing env CLIPPY_DRIVER"))
++ .args(args)
++ .stdout(Stdio::inherit())
++ .output()
++ .expect("failed to run clippy-driver");
++
++ stream
++ .get_mut()
++ .write_all(&output.stderr)
++ .unwrap_or_else(|e| panic!("{e:?} in {driver_info:?}"));
++
++ match output.status.code() {
++ Some(0) => Some(0),
++ code => {
++ io::stderr().write_all(&output.stderr).unwrap();
++ Some(code.expect("killed by signal"))
++ },
++ }
++}
++
++pub fn drive(addr: &str) {
++ process::exit(run_clippy(addr).unwrap_or_else(|| {
++ Command::new("rustc")
++ .args(env::args_os().skip(2))
++ .status()
++ .unwrap()
++ .code()
++ .unwrap()
++ }))
++}
--- /dev/null
- use config::LintcheckConfig;
+// 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 std::collections::HashMap;
++use crate::config::LintcheckConfig;
++use crate::recursive::LintcheckServer;
+
- use std::fs::write;
++use std::collections::{HashMap, HashSet};
+use std::env;
++use std::env::consts::EXE_SUFFIX;
+use std::fmt::Write as _;
- use cargo_metadata::diagnostic::DiagnosticLevel;
++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;
+
- #[cfg(not(windows))]
- const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver";
- #[cfg(not(windows))]
- const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy";
-
- #[cfg(windows)]
- const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver.exe";
- #[cfg(windows)]
- const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy.exe";
-
++use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};
+use cargo_metadata::Message;
+use rayon::prelude::*;
+use serde::{Deserialize, Serialize};
+use walkdir::{DirEntry, WalkDir};
+
- fn new(cargo_message: Message, krate: &Crate) -> Option<Self> {
- let diag = match cargo_message {
- Message::CompilerMessage(message) => message.message,
- _ => return None,
- };
-
+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 {
- krate.name, krate.version, span.file_name
++ 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 = match Path::new(&span.file_name).strip_prefix(env!("CARGO_HOME")) {
+ Ok(stripped) => format!("$CARGO_HOME/{}", stripped.display()),
+ Err(_) => format!(
+ "target/lintcheck/sources/{}-{}/{}",
- crate_name: krate.name.clone(),
++ crate_name, crate_version, span.file_name
+ ),
+ };
+
+ Some(Self {
- let lint = format!("`{}`", self.lint_type);
-
++ 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 _ = write!(output, r#" | {:<50} | "{}" |"#, lint, self.message);
+ let mut file = self.file.clone();
+ if !file.starts_with('$') {
+ file.insert_str(0, "../");
+ }
+
+ let mut output = String::from("| ");
+ let _ = write!(output, "[`{}`]({}#L{})", file_with_pos, file, self.line);
- let mut args = if config.fix {
++ let _ = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message);
+ output.push('\n');
+ output
+ } else {
+ format!("{} {} \"{}\"\n", file_with_pos, self.lint_type, self.message)
+ }
+ }
+}
+
+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 {} seconds...", retries);
+ thread::sleep(Duration::from_secs(retries as u64));
+ 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/{}/{}/download", name, version);
+ println!("Downloading and extracting {} {} from {}", name, version, url);
+ create_dirs(&krate_download_dir, &extract_dir);
+
+ let krate_file_path = krate_download_dir.join(format!("{}-{}.crate.tar.gz", name, version));
+ // 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!("{}-git", name));
+ repo_path
+ };
+ // clone the repo if we have not done so
+ if !repo_path.is_dir() {
+ println!("Cloning {} and checking out {}", url, commit);
+ if !Command::new("git")
+ .arg("clone")
+ .arg(url)
+ .arg(&repo_path)
+ .status()
+ .expect("Failed to clone git repo!")
+ .success()
+ {
+ eprintln!("Failed to clone {} into {}", url, 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 {} of repo at {}", commit, repo_path.display())
+ }
+
+ Crate {
+ version: commit.clone(),
+ name: name.clone(),
+ path: repo_path,
+ options: options.clone(),
+ }
+ },
+ CrateSource::Path { name, path, options } => {
+ // 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 {:?} to {:?}", path, dest_crate_root);
+
+ 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)
+ }
+
+ 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
+ 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");
+
- args.push(opt);
++ 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 {
- args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
++ clippy_args.push(opt);
+ }
+ } else {
- args.push("--cap-lints=warn");
++ clippy_args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
+ }
+
+ if lint_filter.is_empty() {
- args.push("--cap-lints=allow");
- args.extend(lint_filter.iter().map(|filter| filter.as_str()))
++ clippy_args.push("--cap-lints=warn");
+ } else {
- let all_output = std::process::Command::new(&cargo_clippy_path)
++ clippy_args.push("--cap-lints=allow");
++ clippy_args.extend(lint_filter.iter().map(|filter| filter.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();
+ }
+
- // lint warnings will look like this:
- // src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter`
- .args(&args)
++ 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)),
+ )
- .filter_map(|msg| ClippyWarning::new(msg.unwrap(), &self))
++ .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 {}",
+ self.name, subcrate
+ );
+ }
+ // 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())
- /// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy
- fn read_crates(toml_path: &Path) -> Vec<CrateSource> {
++ .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);
+ }
+}
+
- crate_sources
++/// 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{}", toml_path.display(), e));
+ // parse the hashmap of the toml file into a list of crates
+ let tomlcrates: Vec<TomlCrate> = crate_list
+ .crates
+ .into_iter()
+ .map(|(_cratename, tomlcrate)| tomlcrate)
+ .collect();
+
+ // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
+ // multiple Cratesources)
+ let mut crate_sources = Vec::new();
+ tomlcrates.into_iter().for_each(|tk| {
+ 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
+ versions.iter().for_each(|ver| {
+ 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);
+ if tk.git_hash.is_some() != tk.git_url.is_some() {
+ panic!("Error: Encountered TomlCrate with only one of git_hash and git_url!");
+ }
+ if tk.path.is_some() && (tk.git_hash.is_some() || tk.versions.is_some()) {
+ panic!("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();
+
- fn lintcheck_needs_rerun(lintcheck_logs_path: &Path) -> bool {
++ (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!("{:0>4}, {}", count, lint));
+
+ let mut header = String::from("| lint | count |\n");
+ header.push_str("| -------------------------------------------------- | ----- |\n");
+ let stats_string = stats
+ .iter()
+ .map(|(lint, count)| format!("| {:<50} | {:>4} |\n", lint, count))
+ .fold(header, |mut table, line| {
+ table.push_str(&line);
+ table
+ });
+
+ (stats_string, counter)
+}
+
+/// 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
- let mut times = [CLIPPY_DRIVER_PATH, CARGO_CLIPPY_PATH].iter().map(|p| {
++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 = {
- std::cmp::max(times.next().unwrap(), times.next().unwrap())
++ 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
- if lintcheck_needs_rerun(&config.lintcheck_results_path) {
++ 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
+}
+
+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();
++
+ // if the clippy bin is newer than our logs, throw away target dirs to force clippy to
+ // refresh the logs
- let cargo_clippy_path: PathBuf = PathBuf::from(CARGO_CLIPPY_PATH)
- .canonicalize()
- .expect("failed to canonicalize path to clippy binary");
-
++ 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");
+ }
+ }
+ }
+
- let clippy_ver = std::process::Command::new(CARGO_CLIPPY_PATH)
+ // assert that clippy is found
+ assert!(
+ cargo_clippy_path.is_file(),
+ "target/debug/cargo-clippy binary not found! {}",
+ cargo_clippy_path.display()
+ );
+
- let crates = read_crates(&config.sources_toml_path);
++ 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 clippy_warnings: Vec<ClippyWarning> = crates
++ 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();
+
- .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, crates.len(), &config, &lint_filter))
++ 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()
- std::fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap();
- write(&config.lintcheck_results_path, text).unwrap();
++ .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.iter() {
+ 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
+ same_in_both_hashmaps.iter().for_each(|(k, v)| {
+ 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!("{} 0 => {}", new_key, 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!("{} {} => 0", old_key, old_value);
+ });
+}
+
+/// 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| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create lintcheck target dir");
+ }
+ });
+ std::fs::create_dir(&krate_download_dir).unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create crate download dir");
+ }
+ });
+ std::fs::create_dir(&extract_dir).unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("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
--- /dev/null
++//! In `--recursive` mode we set the `lintcheck` binary as the `RUSTC_WRAPPER` of `cargo check`,
++//! this allows [crate::driver] to be run for every dependency. The driver connects to
++//! [LintcheckServer] to ask if it should be skipped, and if not sends the stderr of running clippy
++//! on the crate to the server
++
++use crate::ClippyWarning;
++use crate::RecursiveOptions;
++
++use std::collections::HashSet;
++use std::io::{BufRead, BufReader, Read, Write};
++use std::net::{SocketAddr, TcpListener, TcpStream};
++use std::sync::{Arc, Mutex};
++use std::thread;
++
++use cargo_metadata::diagnostic::Diagnostic;
++use crossbeam_channel::{Receiver, Sender};
++use serde::de::DeserializeOwned;
++use serde::{Deserialize, Serialize};
++
++#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize)]
++pub(crate) struct DriverInfo {
++ pub package_name: String,
++ pub crate_name: String,
++ pub version: String,
++}
++
++pub(crate) fn serialize_line<T, W>(value: &T, writer: &mut W)
++where
++ T: Serialize,
++ W: Write,
++{
++ let mut buf = serde_json::to_vec(&value).expect("failed to serialize");
++ buf.push(b'\n');
++ writer.write_all(&buf).expect("write_all failed");
++}
++
++pub(crate) fn deserialize_line<T, R>(reader: &mut R) -> T
++where
++ T: DeserializeOwned,
++ R: BufRead,
++{
++ let mut string = String::new();
++ reader.read_line(&mut string).expect("read_line failed");
++ serde_json::from_str(&string).expect("failed to deserialize")
++}
++
++fn process_stream(
++ stream: TcpStream,
++ sender: &Sender<ClippyWarning>,
++ options: &RecursiveOptions,
++ seen: &Mutex<HashSet<DriverInfo>>,
++) {
++ let mut stream = BufReader::new(stream);
++
++ let driver_info: DriverInfo = deserialize_line(&mut stream);
++
++ let unseen = seen.lock().unwrap().insert(driver_info.clone());
++ let ignored = options.ignore.contains(&driver_info.package_name);
++ let should_run = unseen && !ignored;
++
++ serialize_line(&should_run, stream.get_mut());
++
++ let mut stderr = String::new();
++ stream.read_to_string(&mut stderr).unwrap();
++
++ let messages = stderr
++ .lines()
++ .filter_map(|json_msg| serde_json::from_str::<Diagnostic>(json_msg).ok())
++ .filter_map(|diag| ClippyWarning::new(diag, &driver_info.package_name, &driver_info.version));
++
++ for message in messages {
++ sender.send(message).unwrap();
++ }
++}
++
++pub(crate) struct LintcheckServer {
++ pub local_addr: SocketAddr,
++ receiver: Receiver<ClippyWarning>,
++ sender: Arc<Sender<ClippyWarning>>,
++}
++
++impl LintcheckServer {
++ pub fn spawn(options: RecursiveOptions) -> Self {
++ let listener = TcpListener::bind("localhost:0").unwrap();
++ let local_addr = listener.local_addr().unwrap();
++
++ let (sender, receiver) = crossbeam_channel::unbounded::<ClippyWarning>();
++ let sender = Arc::new(sender);
++ // The spawned threads hold a `Weak<Sender>` so that they don't keep the channel connected
++ // indefinitely
++ let sender_weak = Arc::downgrade(&sender);
++
++ // Ignore dependencies multiple times, e.g. for when it's both checked and compiled for a
++ // build dependency
++ let seen = Mutex::default();
++
++ thread::spawn(move || {
++ thread::scope(|s| {
++ s.spawn(|| {
++ while let Ok((stream, _)) = listener.accept() {
++ let sender = sender_weak.upgrade().expect("received connection after server closed");
++ let options = &options;
++ let seen = &seen;
++ s.spawn(move || process_stream(stream, &sender, options, seen));
++ }
++ });
++ });
++ });
++
++ Self {
++ local_addr,
++ sender,
++ receiver,
++ }
++ }
++
++ pub fn warnings(self) -> impl Iterator<Item = ClippyWarning> {
++ // causes the channel to become disconnected so that the receiver iterator ends
++ drop(self.sender);
++
++ self.receiver.into_iter()
++ }
++}
--- /dev/null
- channel = "nightly-2022-09-08"
+[toolchain]
++channel = "nightly-2022-10-06"
+components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
--- /dev/null
- version = "0.2.0"
+[package]
+name = "rustc_tools_util"
++version = "0.2.1"
+description = "small helper to generate version information for git packages"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["rustc", "tool", "git", "version", "hash"]
+categories = ["development-tools"]
+edition = "2018"
+
+[dependencies]
+
+[features]
+deny-warnings = []
--- /dev/null
- ````
+# rustc_tools_util
+
+A small tool to help you generate version information
+for packages installed from a git repo
+
+## Usage
+
+Add a `build.rs` file to your repo and list it in `Cargo.toml`
- ````
++````toml
+build = "build.rs"
+````
+
+List rustc_tools_util as regular AND build dependency.
- rustc_tools_util = "0.1"
++````toml
+[dependencies]
- rustc_tools_util = "0.1"
++rustc_tools_util = "0.2.1"
+
+[build-dependencies]
++rustc_tools_util = "0.2.1"
+````
+
+In `build.rs`, generate the data in your `main()`
+````rust
+fn main() {
+ println!(
+ "cargo:rustc-env=GIT_HASH={}",
+ rustc_tools_util::get_commit_hash().unwrap_or_default()
+ );
+ println!(
+ "cargo:rustc-env=COMMIT_DATE={}",
+ rustc_tools_util::get_commit_date().unwrap_or_default()
+ );
+ println!(
+ "cargo:rustc-env=RUSTC_RELEASE_CHANNEL={}",
+ rustc_tools_util::get_channel().unwrap_or_default()
+ );
+}
+
+````
+
+Use the version information in your main.rs
+````rust
+use rustc_tools_util::*;
+
+fn show_version() {
+ let version_info = rustc_tools_util::get_version_info!();
+ println!("{}", version_info);
+}
+````
+This gives the following output in clippy:
+`clippy 0.0.212 (a416c5e 2018-12-14)`
+
+
+## License
+
+Copyright 2014-2022 The Rust Project Developers
+
+Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+option. All files in the project carrying such notice may not be
+copied, modified, or distributed except according to those terms.
--- /dev/null
- "{} {}.{}.{} ({} {})",
- self.crate_name, self.major, self.minor, self.patch, hash_trimmed, date_trimmed,
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+
+use std::env;
+
+#[macro_export]
+macro_rules! get_version_info {
+ () => {{
+ let major = env!("CARGO_PKG_VERSION_MAJOR").parse::<u8>().unwrap();
+ let minor = env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap();
+ let patch = env!("CARGO_PKG_VERSION_PATCH").parse::<u16>().unwrap();
+ let crate_name = String::from(env!("CARGO_PKG_NAME"));
+
+ let host_compiler = option_env!("RUSTC_RELEASE_CHANNEL").map(str::to_string);
+ let commit_hash = option_env!("GIT_HASH").map(str::to_string);
+ let commit_date = option_env!("COMMIT_DATE").map(str::to_string);
+
+ VersionInfo {
+ major,
+ minor,
+ patch,
+ host_compiler,
+ commit_hash,
+ commit_date,
+ crate_name,
+ }
+ }};
+}
+
+// some code taken and adapted from RLS and cargo
+pub struct VersionInfo {
+ pub major: u8,
+ pub minor: u8,
+ pub patch: u16,
+ pub host_compiler: Option<String>,
+ pub commit_hash: Option<String>,
+ pub commit_date: Option<String>,
+ pub crate_name: String,
+}
+
+impl std::fmt::Display for VersionInfo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let hash = self.commit_hash.clone().unwrap_or_default();
+ let hash_trimmed = hash.trim();
+
+ let date = self.commit_date.clone().unwrap_or_default();
+ let date_trimmed = date.trim();
+
+ if (hash_trimmed.len() + date_trimmed.len()) > 0 {
+ write!(
+ f,
- assert_eq!(vi.patch, 0);
++ "{} {}.{}.{} ({hash_trimmed} {date_trimmed})",
++ self.crate_name, self.major, self.minor, self.patch,
+ )?;
+ } else {
+ write!(f, "{} {}.{}.{}", self.crate_name, self.major, self.minor, self.patch)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl std::fmt::Debug for VersionInfo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "VersionInfo {{ crate_name: \"{}\", major: {}, minor: {}, patch: {}",
+ self.crate_name, self.major, self.minor, self.patch,
+ )?;
+ if self.commit_hash.is_some() {
+ write!(
+ f,
+ ", commit_hash: \"{}\", commit_date: \"{}\" }}",
+ self.commit_hash.clone().unwrap_or_default().trim(),
+ self.commit_date.clone().unwrap_or_default().trim()
+ )?;
+ } else {
+ write!(f, " }}")?;
+ }
+
+ Ok(())
+ }
+}
+
+#[must_use]
+pub fn get_commit_hash() -> Option<String> {
+ std::process::Command::new("git")
+ .args(["rev-parse", "--short", "HEAD"])
+ .output()
+ .ok()
+ .and_then(|r| String::from_utf8(r.stdout).ok())
+}
+
+#[must_use]
+pub fn get_commit_date() -> Option<String> {
+ std::process::Command::new("git")
+ .args(["log", "-1", "--date=short", "--pretty=format:%cd"])
+ .output()
+ .ok()
+ .and_then(|r| String::from_utf8(r.stdout).ok())
+}
+
+#[must_use]
+pub fn get_channel() -> String {
+ match env::var("CFG_RELEASE_CHANNEL") {
+ Ok(channel) => channel,
+ Err(_) => {
+ // if that failed, try to ask rustc -V, do some parsing and find out
+ match std::process::Command::new("rustc")
+ .arg("-V")
+ .output()
+ .ok()
+ .and_then(|r| String::from_utf8(r.stdout).ok())
+ {
+ Some(rustc_output) => {
+ if rustc_output.contains("beta") {
+ String::from("beta")
+ } else if rustc_output.contains("stable") {
+ String::from("stable")
+ } else {
+ // default to nightly if we fail to parse
+ String::from("nightly")
+ }
+ },
+ // default to nightly
+ None => String::from("nightly"),
+ }
+ },
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_struct_local() {
+ let vi = get_version_info!();
+ assert_eq!(vi.major, 0);
+ assert_eq!(vi.minor, 2);
- assert_eq!(vi.to_string(), "rustc_tools_util 0.2.0");
++ assert_eq!(vi.patch, 1);
+ assert_eq!(vi.crate_name, "rustc_tools_util");
+ // hard to make positive tests for these since they will always change
+ assert!(vi.commit_hash.is_none());
+ assert!(vi.commit_date.is_none());
+ }
+
+ #[test]
+ fn test_display_local() {
+ let vi = get_version_info!();
- let s = format!("{:?}", vi);
++ assert_eq!(vi.to_string(), "rustc_tools_util 0.2.1");
+ }
+
+ #[test]
+ fn test_debug_local() {
+ let vi = get_version_info!();
- "VersionInfo { crate_name: \"rustc_tools_util\", major: 0, minor: 2, patch: 0 }"
++ let s = format!("{vi:?}");
+ assert_eq!(
+ s,
++ "VersionInfo { crate_name: \"rustc_tools_util\", major: 0, minor: 2, patch: 1 }"
+ );
+ }
+}
--- /dev/null
+// autogenerated. Please look at /clippy_dev/src/update_lints.rs
+
+macro_rules! include_lint {
+ ($file_name: expr) => {
+ include_str!($file_name)
+ };
+}
+
+macro_rules! docs {
+ ($($lint_name: expr,)*) => {
+ pub fn explain(lint: &str) {
+ println!("{}", match lint {
+ $(
+ $lint_name => include_lint!(concat!("docs/", concat!($lint_name, ".txt"))),
+ )*
+ _ => "unknown lint",
+ })
+ }
+ }
+}
+
+docs! {
+ "absurd_extreme_comparisons",
+ "alloc_instead_of_core",
+ "allow_attributes_without_reason",
+ "almost_complete_letter_range",
+ "almost_swapped",
+ "approx_constant",
+ "arithmetic_side_effects",
+ "as_conversions",
+ "as_underscore",
+ "assertions_on_constants",
+ "assertions_on_result_states",
+ "assign_op_pattern",
+ "async_yields_async",
+ "await_holding_invalid_type",
+ "await_holding_lock",
+ "await_holding_refcell_ref",
+ "bad_bit_mask",
+ "bind_instead_of_map",
+ "blanket_clippy_restriction_lints",
+ "blocks_in_if_conditions",
+ "bool_assert_comparison",
+ "bool_comparison",
+ "bool_to_int_with_if",
+ "borrow_as_ptr",
+ "borrow_deref_ref",
+ "borrow_interior_mutable_const",
+ "borrowed_box",
+ "box_collection",
++ "box_default",
+ "boxed_local",
+ "branches_sharing_code",
+ "builtin_type_shadow",
+ "bytes_count_to_len",
+ "bytes_nth",
+ "cargo_common_metadata",
+ "case_sensitive_file_extension_comparisons",
+ "cast_abs_to_unsigned",
+ "cast_enum_constructor",
+ "cast_enum_truncation",
+ "cast_lossless",
+ "cast_possible_truncation",
+ "cast_possible_wrap",
+ "cast_precision_loss",
+ "cast_ptr_alignment",
+ "cast_ref_to_mut",
+ "cast_sign_loss",
+ "cast_slice_different_sizes",
+ "cast_slice_from_raw_parts",
+ "char_lit_as_u8",
+ "chars_last_cmp",
+ "chars_next_cmp",
+ "checked_conversions",
+ "clone_double_ref",
+ "clone_on_copy",
+ "clone_on_ref_ptr",
+ "cloned_instead_of_copied",
+ "cmp_nan",
+ "cmp_null",
+ "cmp_owned",
+ "cognitive_complexity",
+ "collapsible_else_if",
+ "collapsible_if",
+ "collapsible_match",
+ "collapsible_str_replace",
+ "comparison_chain",
+ "comparison_to_empty",
+ "copy_iterator",
+ "crate_in_macro_def",
+ "create_dir",
+ "crosspointer_transmute",
+ "dbg_macro",
+ "debug_assert_with_mut_call",
+ "decimal_literal_representation",
+ "declare_interior_mutable_const",
+ "default_instead_of_iter_empty",
+ "default_numeric_fallback",
+ "default_trait_access",
+ "default_union_representation",
+ "deprecated_cfg_attr",
+ "deprecated_semver",
+ "deref_addrof",
+ "deref_by_slicing",
+ "derivable_impls",
+ "derive_hash_xor_eq",
+ "derive_ord_xor_partial_ord",
+ "derive_partial_eq_without_eq",
++ "disallowed_macros",
+ "disallowed_methods",
+ "disallowed_names",
+ "disallowed_script_idents",
+ "disallowed_types",
+ "diverging_sub_expression",
+ "doc_link_with_quotes",
+ "doc_markdown",
+ "double_comparisons",
+ "double_must_use",
+ "double_neg",
+ "double_parens",
+ "drop_copy",
+ "drop_non_drop",
+ "drop_ref",
+ "duplicate_mod",
+ "duplicate_underscore_argument",
+ "duration_subsec",
+ "else_if_without_else",
+ "empty_drop",
+ "empty_enum",
+ "empty_line_after_outer_attr",
+ "empty_loop",
+ "empty_structs_with_brackets",
+ "enum_clike_unportable_variant",
+ "enum_glob_use",
+ "enum_variant_names",
+ "eq_op",
+ "equatable_if_let",
+ "erasing_op",
+ "err_expect",
+ "excessive_precision",
+ "exhaustive_enums",
+ "exhaustive_structs",
+ "exit",
+ "expect_fun_call",
+ "expect_used",
+ "expl_impl_clone_on_copy",
+ "explicit_auto_deref",
+ "explicit_counter_loop",
+ "explicit_deref_methods",
+ "explicit_into_iter_loop",
+ "explicit_iter_loop",
+ "explicit_write",
+ "extend_with_drain",
+ "extra_unused_lifetimes",
+ "fallible_impl_from",
+ "field_reassign_with_default",
+ "filetype_is_file",
+ "filter_map_identity",
+ "filter_map_next",
+ "filter_next",
+ "flat_map_identity",
+ "flat_map_option",
+ "float_arithmetic",
+ "float_cmp",
+ "float_cmp_const",
+ "float_equality_without_abs",
+ "fn_address_comparisons",
+ "fn_params_excessive_bools",
+ "fn_to_numeric_cast",
+ "fn_to_numeric_cast_any",
+ "fn_to_numeric_cast_with_truncation",
+ "for_kv_map",
+ "for_loops_over_fallibles",
+ "forget_copy",
+ "forget_non_drop",
+ "forget_ref",
+ "format_in_format_args",
+ "format_push_string",
+ "from_iter_instead_of_collect",
+ "from_over_into",
+ "from_str_radix_10",
+ "future_not_send",
+ "get_first",
+ "get_last_with_len",
+ "get_unwrap",
+ "identity_op",
+ "if_let_mutex",
+ "if_not_else",
+ "if_same_then_else",
+ "if_then_some_else_none",
+ "ifs_same_cond",
+ "implicit_clone",
+ "implicit_hasher",
+ "implicit_return",
++ "implicit_saturating_add",
+ "implicit_saturating_sub",
+ "imprecise_flops",
+ "inconsistent_digit_grouping",
+ "inconsistent_struct_constructor",
+ "index_refutable_slice",
+ "indexing_slicing",
+ "ineffective_bit_mask",
+ "inefficient_to_string",
+ "infallible_destructuring_match",
+ "infinite_iter",
+ "inherent_to_string",
+ "inherent_to_string_shadow_display",
+ "init_numbered_fields",
+ "inline_always",
+ "inline_asm_x86_att_syntax",
+ "inline_asm_x86_intel_syntax",
+ "inline_fn_without_body",
+ "inspect_for_each",
+ "int_plus_one",
+ "integer_arithmetic",
+ "integer_division",
+ "into_iter_on_ref",
+ "invalid_null_ptr_usage",
+ "invalid_regex",
+ "invalid_upcast_comparisons",
+ "invalid_utf8_in_unchecked",
+ "invisible_characters",
+ "is_digit_ascii_radix",
+ "items_after_statements",
+ "iter_cloned_collect",
+ "iter_count",
+ "iter_kv_map",
+ "iter_next_loop",
+ "iter_next_slice",
+ "iter_not_returning_iterator",
+ "iter_nth",
+ "iter_nth_zero",
+ "iter_on_empty_collections",
+ "iter_on_single_items",
+ "iter_overeager_cloned",
+ "iter_skip_next",
+ "iter_with_drain",
+ "iterator_step_by_zero",
+ "just_underscores_and_digits",
+ "large_const_arrays",
+ "large_digit_groups",
+ "large_enum_variant",
+ "large_include_file",
+ "large_stack_arrays",
+ "large_types_passed_by_value",
+ "len_without_is_empty",
+ "len_zero",
+ "let_and_return",
+ "let_underscore_drop",
+ "let_underscore_lock",
+ "let_underscore_must_use",
+ "let_unit_value",
+ "linkedlist",
+ "lossy_float_literal",
+ "macro_use_imports",
+ "main_recursion",
+ "manual_assert",
+ "manual_async_fn",
+ "manual_bits",
++ "manual_clamp",
+ "manual_filter_map",
+ "manual_find",
+ "manual_find_map",
+ "manual_flatten",
+ "manual_instant_elapsed",
+ "manual_map",
+ "manual_memcpy",
+ "manual_non_exhaustive",
+ "manual_ok_or",
+ "manual_range_contains",
+ "manual_rem_euclid",
+ "manual_retain",
+ "manual_saturating_arithmetic",
+ "manual_split_once",
+ "manual_str_repeat",
+ "manual_string_new",
+ "manual_strip",
+ "manual_swap",
+ "manual_unwrap_or",
+ "many_single_char_names",
+ "map_clone",
+ "map_collect_result_unit",
+ "map_entry",
+ "map_err_ignore",
+ "map_flatten",
+ "map_identity",
+ "map_unwrap_or",
+ "match_as_ref",
+ "match_bool",
+ "match_like_matches_macro",
+ "match_on_vec_items",
+ "match_overlapping_arm",
+ "match_ref_pats",
+ "match_result_ok",
+ "match_same_arms",
+ "match_single_binding",
+ "match_str_case_mismatch",
+ "match_wild_err_arm",
+ "match_wildcard_for_single_variants",
+ "maybe_infinite_iter",
+ "mem_forget",
+ "mem_replace_option_with_none",
+ "mem_replace_with_default",
+ "mem_replace_with_uninit",
+ "min_max",
+ "mismatched_target_os",
+ "mismatching_type_param_order",
+ "misrefactored_assign_op",
+ "missing_const_for_fn",
+ "missing_docs_in_private_items",
+ "missing_enforced_import_renames",
+ "missing_errors_doc",
+ "missing_inline_in_public_items",
+ "missing_panics_doc",
+ "missing_safety_doc",
+ "missing_spin_loop",
+ "mistyped_literal_suffixes",
+ "mixed_case_hex_literals",
+ "mixed_read_write_in_expression",
+ "mod_module_files",
+ "module_inception",
+ "module_name_repetitions",
+ "modulo_arithmetic",
+ "modulo_one",
+ "multi_assignments",
+ "multiple_crate_versions",
+ "multiple_inherent_impl",
+ "must_use_candidate",
+ "must_use_unit",
+ "mut_from_ref",
+ "mut_mut",
+ "mut_mutex_lock",
+ "mut_range_bound",
+ "mutable_key_type",
+ "mutex_atomic",
+ "mutex_integer",
+ "naive_bytecount",
+ "needless_arbitrary_self_type",
+ "needless_bitwise_bool",
+ "needless_bool",
+ "needless_borrow",
+ "needless_borrowed_reference",
+ "needless_collect",
+ "needless_continue",
+ "needless_doctest_main",
+ "needless_for_each",
+ "needless_late_init",
+ "needless_lifetimes",
+ "needless_match",
+ "needless_option_as_deref",
+ "needless_option_take",
+ "needless_parens_on_range_literals",
+ "needless_pass_by_value",
+ "needless_question_mark",
+ "needless_range_loop",
+ "needless_return",
+ "needless_splitn",
+ "needless_update",
+ "neg_cmp_op_on_partial_ord",
+ "neg_multiply",
+ "negative_feature_names",
+ "never_loop",
+ "new_ret_no_self",
+ "new_without_default",
+ "no_effect",
+ "no_effect_replace",
+ "no_effect_underscore_binding",
+ "non_ascii_literal",
+ "non_octal_unix_permissions",
+ "non_send_fields_in_send_ty",
+ "nonminimal_bool",
+ "nonsensical_open_options",
+ "nonstandard_macro_braces",
+ "not_unsafe_ptr_arg_deref",
+ "obfuscated_if_else",
+ "octal_escapes",
+ "ok_expect",
+ "only_used_in_recursion",
+ "op_ref",
+ "option_as_ref_deref",
+ "option_env_unwrap",
+ "option_filter_map",
+ "option_if_let_else",
+ "option_map_or_none",
+ "option_map_unit_fn",
+ "option_option",
+ "or_fun_call",
+ "or_then_unwrap",
+ "out_of_bounds_indexing",
+ "overflow_check_conditional",
+ "overly_complex_bool_expr",
+ "panic",
+ "panic_in_result_fn",
+ "panicking_unwrap",
+ "partialeq_ne_impl",
+ "partialeq_to_none",
+ "path_buf_push_overwrite",
+ "pattern_type_mismatch",
+ "possible_missing_comma",
+ "precedence",
+ "print_in_format_impl",
+ "print_literal",
+ "print_stderr",
+ "print_stdout",
+ "print_with_newline",
+ "println_empty_string",
+ "ptr_arg",
+ "ptr_as_ptr",
+ "ptr_eq",
+ "ptr_offset_with_cast",
+ "pub_use",
+ "question_mark",
+ "range_minus_one",
+ "range_plus_one",
+ "range_zip_with_len",
+ "rc_buffer",
+ "rc_clone_in_vec_init",
+ "rc_mutex",
+ "read_zero_byte_vec",
+ "recursive_format_impl",
+ "redundant_allocation",
+ "redundant_clone",
+ "redundant_closure",
+ "redundant_closure_call",
+ "redundant_closure_for_method_calls",
+ "redundant_else",
+ "redundant_feature_names",
+ "redundant_field_names",
+ "redundant_pattern",
+ "redundant_pattern_matching",
+ "redundant_pub_crate",
+ "redundant_slicing",
+ "redundant_static_lifetimes",
+ "ref_binding_to_reference",
+ "ref_option_ref",
+ "repeat_once",
+ "rest_pat_in_fully_bound_structs",
+ "result_large_err",
+ "result_map_or_into_option",
+ "result_map_unit_fn",
+ "result_unit_err",
+ "return_self_not_must_use",
+ "reversed_empty_ranges",
+ "same_functions_in_if_condition",
+ "same_item_push",
+ "same_name_method",
+ "search_is_some",
+ "self_assignment",
+ "self_named_constructors",
+ "self_named_module_files",
+ "semicolon_if_nothing_returned",
+ "separated_literal_suffix",
+ "serde_api_misuse",
+ "shadow_reuse",
+ "shadow_same",
+ "shadow_unrelated",
+ "short_circuit_statement",
+ "should_implement_trait",
+ "significant_drop_in_scrutinee",
+ "similar_names",
+ "single_char_add_str",
+ "single_char_lifetime_names",
+ "single_char_pattern",
+ "single_component_path_imports",
+ "single_element_loop",
+ "single_match",
+ "single_match_else",
+ "size_of_in_element_count",
+ "skip_while_next",
+ "slow_vector_initialization",
+ "stable_sort_primitive",
+ "std_instead_of_alloc",
+ "std_instead_of_core",
+ "str_to_string",
+ "string_add",
+ "string_add_assign",
+ "string_extend_chars",
+ "string_from_utf8_as_bytes",
+ "string_lit_as_bytes",
+ "string_slice",
+ "string_to_string",
+ "strlen_on_c_strings",
+ "struct_excessive_bools",
+ "suboptimal_flops",
+ "suspicious_arithmetic_impl",
+ "suspicious_assignment_formatting",
+ "suspicious_else_formatting",
+ "suspicious_map",
+ "suspicious_op_assign_impl",
+ "suspicious_operation_groupings",
+ "suspicious_splitn",
+ "suspicious_to_owned",
+ "suspicious_unary_op_formatting",
+ "swap_ptr_to_ref",
+ "tabs_in_doc_comments",
+ "temporary_assignment",
+ "to_digit_is_some",
+ "to_string_in_format_args",
+ "todo",
+ "too_many_arguments",
+ "too_many_lines",
+ "toplevel_ref_arg",
+ "trailing_empty_array",
+ "trait_duplication_in_bounds",
+ "transmute_bytes_to_str",
+ "transmute_float_to_int",
+ "transmute_int_to_bool",
+ "transmute_int_to_char",
+ "transmute_int_to_float",
+ "transmute_num_to_bytes",
+ "transmute_ptr_to_ptr",
+ "transmute_ptr_to_ref",
+ "transmute_undefined_repr",
+ "transmutes_expressible_as_ptr_casts",
+ "transmuting_null",
+ "trim_split_whitespace",
+ "trivial_regex",
+ "trivially_copy_pass_by_ref",
+ "try_err",
+ "type_complexity",
+ "type_repetition_in_bounds",
+ "undocumented_unsafe_blocks",
+ "undropped_manually_drops",
+ "unicode_not_nfc",
+ "unimplemented",
+ "uninit_assumed_init",
+ "uninit_vec",
++ "uninlined_format_args",
+ "unit_arg",
+ "unit_cmp",
+ "unit_hash",
+ "unit_return_expecting_ord",
+ "unnecessary_cast",
+ "unnecessary_filter_map",
+ "unnecessary_find_map",
+ "unnecessary_fold",
+ "unnecessary_join",
+ "unnecessary_lazy_evaluations",
+ "unnecessary_mut_passed",
+ "unnecessary_operation",
+ "unnecessary_owned_empty_strings",
+ "unnecessary_self_imports",
+ "unnecessary_sort_by",
+ "unnecessary_to_owned",
+ "unnecessary_unwrap",
+ "unnecessary_wraps",
+ "unneeded_field_pattern",
+ "unneeded_wildcard_pattern",
+ "unnested_or_patterns",
+ "unreachable",
+ "unreadable_literal",
+ "unsafe_derive_deserialize",
+ "unsafe_removed_from_name",
+ "unseparated_literal_suffix",
+ "unsound_collection_transmute",
+ "unused_async",
+ "unused_io_amount",
+ "unused_peekable",
+ "unused_rounding",
+ "unused_self",
+ "unused_unit",
+ "unusual_byte_groupings",
+ "unwrap_in_result",
+ "unwrap_or_else_default",
+ "unwrap_used",
+ "upper_case_acronyms",
+ "use_debug",
+ "use_self",
+ "used_underscore_binding",
+ "useless_asref",
+ "useless_attribute",
+ "useless_conversion",
+ "useless_format",
+ "useless_let_if_seq",
+ "useless_transmute",
+ "useless_vec",
+ "vec_box",
+ "vec_init_then_push",
+ "vec_resize_to_zero",
+ "verbose_bit_mask",
+ "verbose_file_reads",
+ "vtable_address_comparisons",
+ "while_immutable_condition",
+ "while_let_loop",
+ "while_let_on_iterator",
+ "wildcard_dependencies",
+ "wildcard_enum_match_arm",
+ "wildcard_imports",
+ "wildcard_in_or_patterns",
+ "write_literal",
+ "write_with_newline",
+ "writeln_empty_string",
+ "wrong_self_convention",
+ "wrong_transmute",
+ "zero_divided_by_zero",
+ "zero_prefixed_literal",
+ "zero_ptr",
+ "zero_sized_map_values",
+ "zst_offset",
+
+}
--- /dev/null
- Known safe built-in types like `Wrapping` or `Saturing`, floats, operations in constant
+### What it does
+Checks any kind of arithmetic operation of any type.
+
+Operators like `+`, `-`, `*` or `<<` are usually capable of overflowing according to the [Rust
+Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
+or can panic (`/`, `%`).
+
++Known safe built-in types like `Wrapping` or `Saturating`, floats, operations in constant
+environments, allowed types and non-constant operations that won't overflow are ignored.
+
+### Why is this bad?
+For integers, overflow will trigger a panic in debug builds or wrap the result in
+release mode; division by zero will cause a panic in either mode. As a result, it is
+desirable to explicitly call checked, wrapping or saturating arithmetic methods.
+
+#### Example
+```
+// `n` can be any number, including `i32::MAX`.
+fn foo(n: i32) -> i32 {
+ n + 1
+}
+```
+
+Third-party types can also overflow or present unwanted side-effects.
+
+#### Example
+```
+use rust_decimal::Decimal;
+let _n = Decimal::MAX + Decimal::MAX;
+```
+
+### Allowed types
+Custom allowed types can be specified through the "arithmetic-side-effects-allowed" filter.
--- /dev/null
--- /dev/null
++### What it does
++checks for `Box::new(T::default())`, which is better written as
++`Box::<T>::default()`.
++
++### Why is this bad?
++First, it's more complex, involving two calls instead of one.
++Second, `Box::default()` can be faster
++[in certain cases](https://nnethercote.github.io/perf-book/standard-library-types.html#box).
++
++### Known problems
++The lint may miss some cases (e.g. Box::new(String::from(""))).
++On the other hand, it will trigger on cases where the `default`
++code comes from a macro that does something different based on
++e.g. target operating system.
++
++### Example
++```
++let x: Box<String> = Box::new(Default::default());
++```
++Use instead:
++```
++let x: Box<String> = Box::default();
++```
--- /dev/null
--- /dev/null
++### 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:
++```
++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,
++}
++```
--- /dev/null
--- /dev/null
++### What it does
++Checks for implicit saturating addition.
++
++### Why is this bad?
++The built-in function is more readable and may be faster.
++
++### Example
++```
++let mut u:u32 = 7000;
++
++if u != u32::MAX {
++ u += 1;
++}
++```
++Use instead:
++```
++let mut u:u32 = 7000;
++
++u = u.saturating_add(1);
++```
--- /dev/null
--- /dev/null
++### What it does
++Identifies good opportunities for a clamp function from std or core, and suggests using it.
++
++### Why is this bad?
++clamp is much shorter, easier to read, and doesn't use any control flow.
++
++### Known issue(s)
++If the clamped variable is NaN this suggestion will cause the code to propagate NaN
++rather than returning either `max` or `min`.
++
++`clamp` functions will panic if `max < min`, `max.is_nan()`, or `min.is_nan()`.
++Some may consider panicking in these situations to be desirable, but it also may
++introduce panicking where there wasn't any before.
++
++### Examples
++```
++if input > max {
++ max
++} else if input < min {
++ min
++} else {
++ input
++}
++```
++
++```
++input.max(min).min(max)
++```
++
++```
++match input {
++ x if x > max => max,
++ x if x < min => min,
++ x => x,
++}
++```
++
++```
++let mut x = input;
++if x < min { x = min; }
++if x > max { x = max; }
++```
++Use instead:
++```
++input.clamp(min, max)
++```
--- /dev/null
- Checks for bindings that destructure a reference and borrow the inner
+### What it does
- ### Known problems
- In some cases, `&ref` is needed to avoid a lifetime mismatch error.
- Example:
- ```
- fn foo(a: &Option<String>, b: &Option<String>) {
- match (a, b) {
- (None, &ref c) | (&ref c, None) => (),
- (&Some(ref c), _) => (),
- };
- }
- ```
-
++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
+```
+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:
+```
+let mut v = Vec::<String>::new();
+v.iter_mut().filter(|a| a.is_empty());
++
++if let [first, second] = v.as_slice() {}
+```
--- /dev/null
+### What it does
+Checks for names that are very similar and thus confusing.
+
++Note: this lint looks for similar names throughout each
++scope. To allow it, you need to allow it on the scope
++level, not on the name that is reported.
++
+### Why is this bad?
+It's hard to distinguish between names that differ only
+by a single character.
+
+### Example
+```
+let checked_exp = something;
+let checked_expr = something_else;
+```
--- /dev/null
--- /dev/null
++### What it does
++Detect when a variable is not inlined in a format string,
++and suggests to inline it.
++
++### Why is this bad?
++Non-inlined code is slightly more difficult to read and understand,
++as it requires arguments to be matched against the format string.
++The inlined syntax, where allowed, is simpler.
++
++### Example
++```
++format!("{}", var);
++format!("{v:?}", v = var);
++format!("{0} {0}", var);
++format!("{0:1$}", var, width);
++format!("{:.*}", prec, var);
++```
++Use instead:
++```
++format!("{var}");
++format!("{var:?}");
++format!("{var} {var}");
++format!("{var:width$}");
++format!("{var:.prec$}");
++```
++
++### Known Problems
++
++There may be a false positive if the format string is expanded from certain proc macros:
++
++```
++println!(indoc!("{}"), var);
++```
++
++If a format string contains a numbered argument that cannot be inlined
++nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
--- /dev/null
- format!("we would appreciate a bug report: {}", bug_report_url).into(),
- format!("Clippy version: {}", version_info).into(),
+#![feature(rustc_private)]
+#![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, PathBuf};
+use std::process::{exit, Command};
+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),
+ ));
+}
+
+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 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);
+ }));
+ 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);
+ }
+
+ let conf = clippy_lints::read_conf(sess);
+ 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,
+ ));
+ 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(),
- println!("{}", version_info);
++ 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);
+}
+
+fn toolchain_path(home: Option<String>, toolchain: Option<String>) -> Option<PathBuf> {
+ home.and_then(|home| {
+ toolchain.map(|toolchain| {
+ let mut path = PathBuf::from(home);
+ path.push("toolchains");
+ path.push(toolchain);
+ path
+ })
+ })
+}
+
+#[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();
+
+ // Get the sysroot, looking from most specific to this invocation to the least:
+ // - command line
+ // - runtime environment
+ // - SYSROOT
+ // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
+ // - sysroot from rustc in the path
+ // - compile-time environment
+ // - SYSROOT
+ // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
+ let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true);
+ let have_sys_root_arg = sys_root_arg.is_some();
+ let sys_root = sys_root_arg
+ .map(PathBuf::from)
+ .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from))
+ .or_else(|| {
+ let home = std::env::var("RUSTUP_HOME")
+ .or_else(|_| std::env::var("MULTIRUST_HOME"))
+ .ok();
+ let toolchain = std::env::var("RUSTUP_TOOLCHAIN")
+ .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN"))
+ .ok();
+ toolchain_path(home, toolchain)
+ })
+ .or_else(|| {
+ Command::new("rustc")
+ .arg("--print")
+ .arg("sysroot")
+ .output()
+ .ok()
+ .and_then(|out| String::from_utf8(out.stdout).ok())
+ .map(|s| PathBuf::from(s.trim()))
+ })
+ .or_else(|| option_env!("SYSROOT").map(PathBuf::from))
+ .or_else(|| {
+ let home = option_env!("RUSTUP_HOME")
+ .or(option_env!("MULTIRUST_HOME"))
+ .map(ToString::to_string);
+ let toolchain = option_env!("RUSTUP_TOOLCHAIN")
+ .or(option_env!("MULTIRUST_TOOLCHAIN"))
+ .map(ToString::to_string);
+ toolchain_path(home, toolchain)
+ })
+ .map(|pb| pb.to_string_lossy().to_string())
+ .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust");
+
+ // 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();
+
+ // if we call "rustc", we need to pass --sysroot here as well
+ let mut args: Vec<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ 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);
+ }
+
+ // this conditional check for the --sysroot flag is there so users can call
+ // `clippy_driver` directly
+ // without having to pass --sysroot or anything
+ let mut args: Vec<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ 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 {
+ 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
- println!("{}", CARGO_CLIPPY_HELP);
+#![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};
+
+mod docs;
+
+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!("{}", version_info);
++ println!("{CARGO_CLIPPY_HELP}");
+}
+
+fn show_version() {
+ let version_info = rustc_tools_util::get_version_info!();
- .map(|arg| format!("{}__CLIPPY_HACKERY__", arg))
++ 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();
+ docs::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
- "dependencies not found in depinfo: {:?}\n\
+#![feature(test)] // compiletest_rs requires this attribute
+#![feature(once_cell)]
+#![feature(is_sorted)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use compiletest_rs as compiletest;
+use compiletest_rs::common::Mode as TestMode;
+
+use std::collections::HashMap;
+use std::env::{self, remove_var, set_var, var_os};
+use std::ffi::{OsStr, OsString};
+use std::fs;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::sync::LazyLock;
+use test_utils::IS_RUSTC_TEST_SUITE;
+
+mod test_utils;
+
+// whether to run internal tests or not
+const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal");
+
+/// All crates used in UI tests are listed here
+static TEST_DEPENDENCIES: &[&str] = &[
+ "clippy_lints",
+ "clippy_utils",
+ "derive_new",
+ "futures",
+ "if_chain",
+ "itertools",
+ "quote",
+ "regex",
+ "serde",
+ "serde_derive",
+ "syn",
+ "tokio",
+ "parking_lot",
+ "rustc_semver",
+];
+
+// Test dependencies may need an `extern crate` here to ensure that they show up
+// in the depinfo file (otherwise cargo thinks they are unused)
+#[allow(unused_extern_crates)]
+extern crate clippy_lints;
+#[allow(unused_extern_crates)]
+extern crate clippy_utils;
+#[allow(unused_extern_crates)]
+extern crate derive_new;
+#[allow(unused_extern_crates)]
+extern crate futures;
+#[allow(unused_extern_crates)]
+extern crate if_chain;
+#[allow(unused_extern_crates)]
+extern crate itertools;
+#[allow(unused_extern_crates)]
+extern crate parking_lot;
+#[allow(unused_extern_crates)]
+extern crate quote;
+#[allow(unused_extern_crates)]
+extern crate rustc_semver;
+#[allow(unused_extern_crates)]
+extern crate syn;
+#[allow(unused_extern_crates)]
+extern crate tokio;
+
+/// Produces a string with an `--extern` flag for all UI test crate
+/// dependencies.
+///
+/// The dependency files are located by parsing the depinfo file for this test
+/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
+/// dependencies must be added to Cargo.toml at the project root. Test
+/// dependencies that are not *directly* used by this test module require an
+/// `extern crate` declaration.
+static EXTERN_FLAGS: LazyLock<String> = LazyLock::new(|| {
+ let current_exe_depinfo = {
+ let mut path = env::current_exe().unwrap();
+ path.set_extension("d");
+ fs::read_to_string(path).unwrap()
+ };
+ let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
+ for line in current_exe_depinfo.lines() {
+ // each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
+ let parse_name_path = || {
+ if line.starts_with(char::is_whitespace) {
+ return None;
+ }
+ let path_str = line.strip_suffix(':')?;
+ let path = Path::new(path_str);
+ if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") {
+ return None;
+ }
+ let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?;
+ // the "lib" prefix is not present for dll files
+ let name = name.strip_prefix("lib").unwrap_or(name);
+ Some((name, path_str))
+ };
+ if let Some((name, path)) = parse_name_path() {
+ if TEST_DEPENDENCIES.contains(&name) {
+ // A dependency may be listed twice if it is available in sysroot,
+ // and the sysroot dependencies are listed first. As of the writing,
+ // this only seems to apply to if_chain.
+ crates.insert(name, path);
+ }
+ }
+ }
+ let not_found: Vec<&str> = TEST_DEPENDENCIES
+ .iter()
+ .copied()
+ .filter(|n| !crates.contains_key(n))
+ .collect();
+ assert!(
+ not_found.is_empty(),
- not_found,
++ "dependencies not found in depinfo: {not_found:?}\n\
+ help: Make sure the `-Z binary-dep-depinfo` rust flag is enabled\n\
+ help: Try adding to dev-dependencies in Cargo.toml\n\
+ help: Be sure to also add `extern crate ...;` to tests/compile-test.rs",
- .map(|(name, path)| format!(" --extern {}={}", name, path))
+ );
+ crates
+ .into_iter()
- "--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}",
++ .map(|(name, path)| format!(" --extern {name}={path}"))
+ .collect()
+});
+
+fn base_config(test_dir: &str) -> compiletest::Config {
+ let mut config = compiletest::Config {
+ edition: Some("2021".into()),
+ mode: TestMode::Ui,
+ ..Default::default()
+ };
+
+ if let Ok(filters) = env::var("TESTNAME") {
+ config.filters = filters.split(',').map(ToString::to_string).collect();
+ }
+
+ if let Some(path) = option_env!("RUSTC_LIB_PATH") {
+ let path = PathBuf::from(path);
+ config.run_lib_path = path.clone();
+ config.compile_lib_path = path;
+ }
+ let current_exe_path = env::current_exe().unwrap();
+ let deps_path = current_exe_path.parent().unwrap();
+ let profile_path = deps_path.parent().unwrap();
+
+ // Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
+ // This is valuable because a) it allows us to monitor what external dependencies are used
+ // and b) it ensures that conflicting rlibs are resolved properly.
+ let host_libs = option_env!("HOST_LIBS")
+ .map(|p| format!(" -L dependency={}", Path::new(p).join("deps").display()))
+ .unwrap_or_default();
+ config.target_rustcflags = Some(format!(
- host_libs,
++ "--emit=metadata -Dwarnings -Zui-testing -L dependency={}{host_libs}{}",
+ deps_path.display(),
- panic!("I/O failure during tests: {:?}", e);
+ &*EXTERN_FLAGS,
+ ));
+
+ config.src_base = Path::new("tests").join(test_dir);
+ config.build_base = profile_path.join("test").join(test_dir);
+ config.rustc_path = profile_path.join(if cfg!(windows) {
+ "clippy-driver.exe"
+ } else {
+ "clippy-driver"
+ });
+ config
+}
+
+fn run_ui() {
+ let mut config = base_config("ui");
+ config.rustfix_coverage = true;
+ // use tests/clippy.toml
+ let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap());
+ let _threads = VarGuard::set(
+ "RUST_TEST_THREADS",
+ // if RUST_TEST_THREADS is set, adhere to it, otherwise override it
+ env::var("RUST_TEST_THREADS").unwrap_or_else(|_| {
+ std::thread::available_parallelism()
+ .map_or(1, std::num::NonZeroUsize::get)
+ .to_string()
+ }),
+ );
+ compiletest::run_tests(&config);
+ check_rustfix_coverage();
+}
+
+fn run_internal_tests() {
+ // only run internal tests with the internal-tests feature
+ if !RUN_INTERNAL_TESTS {
+ return;
+ }
+ let config = base_config("ui-internal");
+ compiletest::run_tests(&config);
+}
+
+fn run_ui_toml() {
+ fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
+ let mut result = true;
+ let opts = compiletest::test_opts(config);
+ for dir in fs::read_dir(&config.src_base)? {
+ let dir = dir?;
+ if !dir.file_type()?.is_dir() {
+ continue;
+ }
+ let dir_path = dir.path();
+ let _g = VarGuard::set("CARGO_MANIFEST_DIR", &dir_path);
+ for file in fs::read_dir(&dir_path)? {
+ let file = file?;
+ let file_path = file.path();
+ if file.file_type()?.is_dir() {
+ continue;
+ }
+ if file_path.extension() != Some(OsStr::new("rs")) {
+ continue;
+ }
+ let paths = compiletest::common::TestPaths {
+ file: file_path,
+ base: config.src_base.clone(),
+ relative_dir: dir_path.file_name().unwrap().into(),
+ };
+ let test_name = compiletest::make_test_name(config, &paths);
+ let index = tests
+ .iter()
+ .position(|test| test.desc.name == test_name)
+ .expect("The test should be in there");
+ result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
+ }
+ }
+ Ok(result)
+ }
+
+ let mut config = base_config("ui-toml");
+ config.src_base = config.src_base.canonicalize().unwrap();
+
+ let tests = compiletest::make_tests(&config);
+
+ let res = run_tests(&config, tests);
+ match res {
+ Ok(true) => {},
+ Ok(false) => panic!("Some tests failed"),
+ Err(e) => {
- panic!("I/O failure during tests: {:?}", e);
++ panic!("I/O failure during tests: {e:?}");
+ },
+ }
+}
+
+fn run_ui_cargo() {
+ fn run_tests(
+ config: &compiletest::Config,
+ filters: &[String],
+ mut tests: Vec<tester::TestDescAndFn>,
+ ) -> Result<bool, io::Error> {
+ let mut result = true;
+ let opts = compiletest::test_opts(config);
+
+ for dir in fs::read_dir(&config.src_base)? {
+ let dir = dir?;
+ if !dir.file_type()?.is_dir() {
+ continue;
+ }
+
+ // Use the filter if provided
+ let dir_path = dir.path();
+ for filter in filters {
+ if !dir_path.ends_with(filter) {
+ continue;
+ }
+ }
+
+ for case in fs::read_dir(&dir_path)? {
+ let case = case?;
+ if !case.file_type()?.is_dir() {
+ continue;
+ }
+
+ let src_path = case.path().join("src");
+
+ // When switching between branches, if the previous branch had a test
+ // that the current branch does not have, the directory is not removed
+ // because an ignored Cargo.lock file exists.
+ if !src_path.exists() {
+ continue;
+ }
+
+ env::set_current_dir(&src_path)?;
+
+ let cargo_toml_path = case.path().join("Cargo.toml");
+ let cargo_content = fs::read(&cargo_toml_path)?;
+ let cargo_parsed: toml::Value = toml::from_str(
+ std::str::from_utf8(&cargo_content).expect("`Cargo.toml` is not a valid utf-8 file!"),
+ )
+ .expect("Can't parse `Cargo.toml`");
+
+ let _g = VarGuard::set("CARGO_MANIFEST_DIR", case.path());
+ let _h = VarGuard::set(
+ "CARGO_PKG_RUST_VERSION",
+ cargo_parsed
+ .get("package")
+ .and_then(|p| p.get("rust-version"))
+ .and_then(toml::Value::as_str)
+ .unwrap_or(""),
+ );
+
+ for file in fs::read_dir(&src_path)? {
+ let file = file?;
+ if file.file_type()?.is_dir() {
+ continue;
+ }
+
+ // Search for the main file to avoid running a test for each file in the project
+ let file_path = file.path();
+ match file_path.file_name().and_then(OsStr::to_str) {
+ Some("main.rs") => {},
+ _ => continue,
+ }
+ let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path());
+ let paths = compiletest::common::TestPaths {
+ file: file_path,
+ base: config.src_base.clone(),
+ relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(),
+ };
+ let test_name = compiletest::make_test_name(config, &paths);
+ let index = tests
+ .iter()
+ .position(|test| test.desc.name == test_name)
+ .expect("The test should be in there");
+ result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
+ }
+ }
+ }
+ Ok(result)
+ }
+
+ if IS_RUSTC_TEST_SUITE {
+ return;
+ }
+
+ let mut config = base_config("ui-cargo");
+ config.src_base = config.src_base.canonicalize().unwrap();
+
+ let tests = compiletest::make_tests(&config);
+
+ let current_dir = env::current_dir().unwrap();
+ let res = run_tests(&config, &config.filters, tests);
+ env::set_current_dir(current_dir).unwrap();
+
+ match res {
+ Ok(true) => {},
+ Ok(false) => panic!("Some tests failed"),
+ Err(e) => {
- assert!(rs_path.starts_with("tests/ui/"), "{:?}", rs_file);
++ panic!("I/O failure during tests: {e:?}");
+ },
+ }
+}
+
+#[test]
+fn compile_test() {
+ set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
+ run_ui();
+ run_ui_toml();
+ run_ui_cargo();
+ run_internal_tests();
+}
+
+const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
+ "assign_ops2.rs",
+ "borrow_deref_ref_unfixable.rs",
+ "cast_size_32bit.rs",
+ "char_lit_as_u8.rs",
+ "cmp_owned/without_suggestion.rs",
+ "dbg_macro.rs",
+ "deref_addrof_double_trigger.rs",
+ "doc/unbalanced_ticks.rs",
+ "eprint_with_newline.rs",
+ "explicit_counter_loop.rs",
+ "iter_skip_next_unfixable.rs",
+ "let_and_return.rs",
+ "literals.rs",
+ "map_flatten.rs",
+ "map_unwrap_or.rs",
+ "match_bool.rs",
+ "mem_replace_macro.rs",
+ "needless_arbitrary_self_type_unfixable.rs",
+ "needless_borrow_pat.rs",
+ "needless_for_each_unfixable.rs",
+ "nonminimal_bool.rs",
+ "print_literal.rs",
+ "print_with_newline.rs",
+ "redundant_static_lifetimes_multiple.rs",
+ "ref_binding_to_reference.rs",
+ "repl_uninit.rs",
+ "result_map_unit_fn_unfixable.rs",
+ "search_is_some.rs",
+ "single_component_path_imports_nested_first.rs",
+ "string_add.rs",
+ "suspicious_to_owned.rs",
+ "toplevel_ref_arg_non_rustfix.rs",
+ "unit_arg.rs",
+ "unnecessary_clone.rs",
+ "unnecessary_lazy_eval_unfixable.rs",
+ "write_literal.rs",
+ "write_literal_2.rs",
+ "write_with_newline.rs",
+];
+
+fn check_rustfix_coverage() {
+ let missing_coverage_path = Path::new("debug/test/ui/rustfix_missing_coverage.txt");
+ let missing_coverage_path = if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR") {
+ PathBuf::from(target_dir).join(missing_coverage_path)
+ } else {
+ missing_coverage_path.to_path_buf()
+ };
+
+ if let Ok(missing_coverage_contents) = std::fs::read_to_string(missing_coverage_path) {
+ assert!(RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS.iter().is_sorted_by_key(Path::new));
+
+ for rs_file in missing_coverage_contents.lines() {
+ let rs_path = Path::new(rs_file);
+ if rs_path.starts_with("tests/ui/crashes") {
+ continue;
+ }
- "`{}` runs `MachineApplicable` diagnostics but is missing a `run-rustfix` annotation. \
++ assert!(rs_path.starts_with("tests/ui/"), "{rs_file:?}");
+ let filename = rs_path.strip_prefix("tests/ui/").unwrap();
+ assert!(
+ RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS
+ .binary_search_by_key(&filename, Path::new)
+ .is_ok(),
- rs_file,
++ "`{rs_file}` runs `MachineApplicable` diagnostics but is missing a `run-rustfix` annotation. \
+ Please either add `// run-rustfix` at the top of the file or add the file to \
+ `RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS` in `tests/compile-test.rs`.",
- "{:?} has incorrect package name",
- path
+ );
+ }
+ }
+}
+
+#[test]
+fn rustfix_coverage_known_exceptions_accuracy() {
+ for filename in RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS {
+ let rs_path = Path::new("tests/ui").join(filename);
+ assert!(
+ rs_path.exists(),
+ "`{}` does not exist",
+ rs_path.strip_prefix(env!("CARGO_MANIFEST_DIR")).unwrap().display()
+ );
+ let fixed_path = rs_path.with_extension("fixed");
+ assert!(
+ !fixed_path.exists(),
+ "`{}` exists",
+ fixed_path.strip_prefix(env!("CARGO_MANIFEST_DIR")).unwrap().display()
+ );
+ }
+}
+
+#[test]
+fn ui_cargo_toml_metadata() {
+ let ui_cargo_path = Path::new("tests/ui-cargo");
+ let cargo_common_metadata_path = ui_cargo_path.join("cargo_common_metadata");
+ let publish_exceptions =
+ ["fail_publish", "fail_publish_true", "pass_publish_empty"].map(|path| cargo_common_metadata_path.join(path));
+
+ for entry in walkdir::WalkDir::new(ui_cargo_path) {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ if path.file_name() != Some(OsStr::new("Cargo.toml")) {
+ continue;
+ }
+
+ let toml = fs::read_to_string(path).unwrap().parse::<toml::Value>().unwrap();
+
+ let package = toml.as_table().unwrap().get("package").unwrap().as_table().unwrap();
+
+ let name = package.get("name").unwrap().as_str().unwrap().replace('-', "_");
+ assert!(
+ path.parent()
+ .unwrap()
+ .components()
+ .map(|component| component.as_os_str().to_string_lossy().replace('-', "_"))
+ .any(|s| *s == name)
+ || path.starts_with(&cargo_common_metadata_path),
- "{:?} lacks `publish = false`",
- path
++ "{path:?} has incorrect package name"
+ );
+
+ let publish = package.get("publish").and_then(toml::Value::as_bool).unwrap_or(true);
+ assert!(
+ !publish || publish_exceptions.contains(&path.parent().unwrap().to_path_buf()),
++ "{path:?} lacks `publish = false`"
+ );
+ }
+}
+
+/// Restores an env var on drop
+#[must_use]
+struct VarGuard {
+ key: &'static str,
+ value: Option<OsString>,
+}
+
+impl VarGuard {
+ fn set(key: &'static str, val: impl AsRef<OsStr>) -> Self {
+ let value = var_os(key);
+ set_var(key, val);
+ Self { key, value }
+ }
+}
+
+impl Drop for VarGuard {
+ fn drop(&mut self) {
+ match self.value.as_deref() {
+ None => remove_var(self.key),
+ Some(value) => set_var(self.key, value),
+ }
+ }
+}
--- /dev/null
- let repo_url = format!("https://github.com/{}", repo_name);
+#![cfg(feature = "integration")]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use std::env;
+use std::ffi::OsStr;
+use std::process::Command;
+
++#[cfg(not(windows))]
++const CARGO_CLIPPY: &str = "cargo-clippy";
++#[cfg(windows)]
++const CARGO_CLIPPY: &str = "cargo-clippy.exe";
++
+#[cfg_attr(feature = "integration", test)]
+fn integration_test() {
+ let repo_name = env::var("INTEGRATION").expect("`INTEGRATION` var not set");
- let clippy_binary = target_dir.join(env!("PROFILE")).join("cargo-clippy");
++ let repo_url = format!("https://github.com/{repo_name}");
+ let crate_name = repo_name
+ .split('/')
+ .nth(1)
+ .expect("repo name should have format `<org>/<name>`");
+
+ let mut repo_dir = tempfile::tempdir().expect("couldn't create temp dir").into_path();
+ repo_dir.push(crate_name);
+
+ let st = Command::new("git")
+ .args([
+ OsStr::new("clone"),
+ OsStr::new("--depth=1"),
+ OsStr::new(&repo_url),
+ OsStr::new(&repo_dir),
+ ])
+ .status()
+ .expect("unable to run git");
+ assert!(st.success());
+
+ let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let target_dir = std::path::Path::new(&root_dir).join("target");
- if stderr.contains("internal compiler error") {
- let backtrace_start = stderr
- .find("thread 'rustc' panicked at")
- .expect("start of backtrace not found");
- let backtrace_end = stderr
- .rfind("error: internal compiler error")
++ let clippy_binary = target_dir.join(env!("PROFILE")).join(CARGO_CLIPPY);
+
+ let output = Command::new(clippy_binary)
+ .current_dir(repo_dir)
+ .env("RUST_BACKTRACE", "full")
+ .env("CARGO_TARGET_DIR", target_dir)
+ .args([
+ "clippy",
+ "--all-targets",
+ "--all-features",
+ "--",
+ "--cap-lints",
+ "warn",
+ "-Wclippy::pedantic",
+ "-Wclippy::nursery",
+ ])
+ .output()
+ .expect("unable to run clippy");
+
+ let stderr = String::from_utf8_lossy(&output.stderr);
- &stderr[backtrace_start..backtrace_end]
++ if let Some(backtrace_start) = stderr.find("error: internal compiler error") {
++ static BACKTRACE_END_MSG: &str = "end of query stack";
++ let backtrace_end = stderr[backtrace_start..]
++ .find(BACKTRACE_END_MSG)
+ .expect("end of backtrace not found");
+
+ panic!(
+ "internal compiler error\nBacktrace:\n\n{}",
- Some(code) => eprintln!("Compilation failed. Exit code: {}", code),
++ &stderr[backtrace_start..backtrace_start + backtrace_end + BACKTRACE_END_MSG.len()]
+ );
+ } else if stderr.contains("query stack during panic") {
+ panic!("query stack during panic in the output");
+ } else if stderr.contains("E0463") {
+ // Encountering E0463 (can't find crate for `x`) did _not_ cause the build to fail in the
+ // past. Even though it should have. That's why we explicitly panic here.
+ // See PR #3552 and issue #3523 for more background.
+ panic!("error: E0463");
+ } else if stderr.contains("E0514") {
+ panic!("incompatible crate versions");
+ } else if stderr.contains("failed to run `rustc` to learn about target-specific information") {
+ panic!("couldn't find librustc_driver, consider setting `LD_LIBRARY_PATH`");
+ } else {
+ assert!(
+ !stderr.contains("toolchain") || !stderr.contains("is not installed"),
+ "missing required toolchain"
+ );
+ }
+
+ match output.status.code() {
+ Some(0) => println!("Compilation successful"),
++ Some(code) => eprintln!("Compilation failed. Exit code: {code}"),
+ None => panic!("Process terminated by signal"),
+ }
+}
--- /dev/null
- message.bad_lines.iter().for_each(|line| eprintln!("{}", line));
+#![feature(once_cell)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use std::ffi::OsStr;
+use std::path::PathBuf;
+use std::sync::LazyLock;
+
+use regex::RegexSet;
+
+#[derive(Debug)]
+struct Message {
+ path: PathBuf,
+ bad_lines: Vec<String>,
+}
+
+impl Message {
+ fn new(path: PathBuf) -> Self {
+ // we don't want the first letter after "error: ", "help: " ... to be capitalized
+ // also no punctuation (except for "?" ?) at the end of a line
+ static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {
+ RegexSet::new([
+ r"error: [A-Z]",
+ r"help: [A-Z]",
+ r"warning: [A-Z]",
+ r"note: [A-Z]",
+ r"try this: [A-Z]",
+ r"error: .*[.!]$",
+ r"help: .*[.!]$",
+ r"warning: .*[.!]$",
+ r"note: .*[.!]$",
+ r"try this: .*[.!]$",
+ ])
+ .unwrap()
+ });
+
+ // sometimes the first character is capitalized and it is legal (like in "C-like enum variants") or
+ // we want to ask a question ending in "?"
+ static EXCEPTIONS_SET: LazyLock<RegexSet> = LazyLock::new(|| {
+ RegexSet::new([
+ r"\.\.\.$",
+ r".*C-like enum variant discriminant is not portable to 32-bit targets",
+ r".*Intel x86 assembly syntax used",
+ r".*AT&T x86 assembly syntax used",
+ r"note: Clippy version: .*",
+ r"the compiler unexpectedly panicked. this is a bug.",
+ ])
+ .unwrap()
+ });
+
+ let content: String = std::fs::read_to_string(&path).unwrap();
+
+ let bad_lines = content
+ .lines()
+ .filter(|line| REGEX_SET.matches(line).matched_any())
+ // ignore exceptions
+ .filter(|line| !EXCEPTIONS_SET.matches(line).matched_any())
+ .map(ToOwned::to_owned)
+ .collect::<Vec<String>>();
+
+ Message { path, bad_lines }
+ }
+}
+
+#[test]
+fn lint_message_convention() {
+ // disable the test inside the rustc test suite
+ if option_env!("RUSTC_TEST_SUITE").is_some() {
+ return;
+ }
+
+ // make sure that lint messages:
+ // * are not capitalized
+ // * don't have punctuation at the end of the last sentence
+
+ // these directories have interesting tests
+ let test_dirs = ["ui", "ui-cargo", "ui-internal", "ui-toml"]
+ .iter()
+ .map(PathBuf::from)
+ .map(|p| {
+ let base = PathBuf::from("tests");
+ base.join(p)
+ });
+
+ // gather all .stderr files
+ let tests = test_dirs
+ .flat_map(|dir| {
+ std::fs::read_dir(dir)
+ .expect("failed to read dir")
+ .map(|direntry| direntry.unwrap().path())
+ })
+ .filter(|file| matches!(file.extension().map(OsStr::to_str), Some(Some("stderr"))));
+
+ // get all files that have any "bad lines" in them
+ let bad_tests: Vec<Message> = tests
+ .map(Message::new)
+ .filter(|message| !message.bad_lines.is_empty())
+ .collect();
+
+ for message in &bad_tests {
+ eprintln!(
+ "error: the test '{}' contained the following nonconforming lines :",
+ message.path.display()
+ );
++ message.bad_lines.iter().for_each(|line| eprintln!("{line}"));
+ eprintln!("\n\n");
+ }
+
+ eprintln!(
+ "\n\n\nLint message should not start with a capital letter and should not have punctuation at the end of the message unless multiple sentences are needed."
+ );
+ eprintln!("Check out the rustc-dev-guide for more information:");
+ eprintln!("https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure\n\n\n");
+
+ assert!(bad_tests.is_empty());
+}
--- /dev/null
- .map(|s| format!("\t{}", s))
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+#![allow(clippy::assertions_on_constants)]
+#![feature(path_file_prefix)]
+
+use std::cmp::Ordering;
+use std::ffi::OsStr;
+use std::fs::{self, DirEntry};
+use std::path::Path;
+
+#[test]
+fn test_missing_tests() {
+ let missing_files = explore_directory(Path::new("./tests"));
+ if !missing_files.is_empty() {
+ assert!(
+ false,
+ "Didn't see a test file for the following files:\n\n{}\n",
+ missing_files
+ .iter()
++ .map(|s| format!("\t{s}"))
+ .collect::<Vec<_>>()
+ .join("\n")
+ );
+ }
+}
+
+// Test for missing files.
+fn explore_directory(dir: &Path) -> Vec<String> {
+ let mut missing_files: Vec<String> = Vec::new();
+ let mut current_file = String::new();
+ let mut files: Vec<DirEntry> = fs::read_dir(dir).unwrap().filter_map(Result::ok).collect();
+ files.sort_by(|x, y| {
+ match x.path().file_prefix().cmp(&y.path().file_prefix()) {
+ Ordering::Equal => (),
+ ord => return ord,
+ }
+ // Sort rs files before the others if they share the same prefix. So when we see
+ // the file prefix for the first time and it's not a rust file, it means the rust
+ // file has to be missing.
+ match (
+ x.path().extension().and_then(OsStr::to_str),
+ y.path().extension().and_then(OsStr::to_str),
+ ) {
+ (Some("rs"), _) => Ordering::Less,
+ (_, Some("rs")) => Ordering::Greater,
+ _ => Ordering::Equal,
+ }
+ });
+ for entry in &files {
+ let path = entry.path();
+ if path.is_dir() {
+ missing_files.extend(explore_directory(&path));
+ } else {
+ let file_prefix = path.file_prefix().unwrap().to_str().unwrap().to_string();
+ if let Some(ext) = path.extension() {
+ match ext.to_str().unwrap() {
+ "rs" => current_file = file_prefix.clone(),
+ "stderr" | "stdout" => {
+ if file_prefix != current_file {
+ missing_files.push(path.to_str().unwrap().to_string());
+ }
+ },
+ _ => continue,
+ };
+ }
+ }
+ }
+ missing_files
+}
--- /dev/null
- = note: `-D clippy::duplicate-mod` implied by `-D warnings`
+error: file is loaded as a module multiple times: `$DIR/b.rs`
+ --> $DIR/main.rs:5:1
+ |
+LL | mod b;
+ | ^^^^^^ first loaded here
+LL | / #[path = "b.rs"]
+LL | | mod b2;
+ | |_______^ loaded again here
+ |
+ = help: replace all but one `mod` item with `use` items
++ = note: `-D clippy::duplicate-mod` implied by `-D warnings`
+
+error: file is loaded as a module multiple times: `$DIR/c.rs`
+ --> $DIR/main.rs:9:1
+ |
+LL | mod c;
+ | ^^^^^^ first loaded here
+LL | / #[path = "c.rs"]
+LL | | mod c2;
+ | |_______^ loaded again here
+LL | / #[path = "c.rs"]
+LL | | mod c3;
+ | |_______^ loaded again here
+ |
+ = help: replace all but one `mod` item with `use` items
+
+error: file is loaded as a module multiple times: `$DIR/d.rs`
+ --> $DIR/main.rs:18:1
+ |
+LL | mod d;
+ | ^^^^^^ first loaded here
+LL | / #[path = "d.rs"]
+LL | | mod d2;
+ | |_______^ loaded again here
+ |
+ = help: replace all but one `mod` item with `use` items
+
+error: file is loaded as a module multiple times: `$DIR/from_other_module.rs`
+ --> $DIR/main.rs:15:1
+ |
+LL | mod from_other_module;
+ | ^^^^^^^^^^^^^^^^^^^^^^ first loaded here
+ |
+ ::: $DIR/other_module/mod.rs:1:1
+ |
+LL | / #[path = "../from_other_module.rs"]
+LL | | mod m;
+ | |______^ loaded again here
+ |
+ = help: replace all but one `mod` item with `use` items
+
+error: aborting due to 4 previous errors
+
--- /dev/null
- = note: `-D clippy::negative-feature-names` implied by `-D warnings`
+error: the "no-" prefix in the feature name "no-qaq" is negative
+ |
- = note: `-D clippy::redundant-feature-names` implied by `-D warnings`
+ = help: consider renaming the feature to "qaq", but make sure the feature adds functionality
++ = note: `-D clippy::negative-feature-names` implied by `-D warnings`
+
+error: the "no_" prefix in the feature name "no_qaq" is negative
+ |
+ = help: consider renaming the feature to "qaq", but make sure the feature adds functionality
+
+error: the "not-" prefix in the feature name "not-orz" is negative
+ |
+ = help: consider renaming the feature to "orz", but make sure the feature adds functionality
+
+error: the "not_" prefix in the feature name "not_orz" is negative
+ |
+ = help: consider renaming the feature to "orz", but make sure the feature adds functionality
+
+error: the "-support" suffix in the feature name "qvq-support" is redundant
+ |
+ = help: consider renaming the feature to "qvq"
++ = note: `-D clippy::redundant-feature-names` implied by `-D warnings`
+
+error: the "_support" suffix in the feature name "qvq_support" is redundant
+ |
+ = help: consider renaming the feature to "qvq"
+
+error: the "use-" prefix in the feature name "use-qwq" is redundant
+ |
+ = help: consider renaming the feature to "qwq"
+
+error: the "use_" prefix in the feature name "use_qwq" is redundant
+ |
+ = help: consider renaming the feature to "qwq"
+
+error: the "with-" prefix in the feature name "with-owo" is redundant
+ |
+ = help: consider renaming the feature to "owo"
+
+error: the "with_" prefix in the feature name "with_owo" is redundant
+ |
+ = help: consider renaming the feature to "owo"
+
+error: aborting due to 10 previous errors
+
--- /dev/null
- = note: `-D clippy::self-named-module-files` implied by `-D warnings`
+error: `mod.rs` files are required, found `bad/inner.rs`
+ --> $DIR/bad/inner.rs:1:1
+ |
+LL | pub mod stuff;
+ | ^
+ |
+ = help: move `bad/inner.rs` to `bad/inner/mod.rs`
++ = note: `-D clippy::self-named-module-files` implied by `-D warnings`
+
+error: `mod.rs` files are required, found `bad/inner/stuff.rs`
+ --> $DIR/bad/inner/stuff.rs:1:1
+ |
+LL | pub mod most;
+ | ^
+ |
+ = help: move `bad/inner/stuff.rs` to `bad/inner/stuff/mod.rs`
+
+error: aborting due to 2 previous errors
+
--- /dev/null
- = note: `-D clippy::self-named-module-files` implied by `-D warnings`
+error: `mod.rs` files are required, found `bad.rs`
+ --> /remapped/module_style/fail_mod_remap/src/bad.rs:1:1
+ |
+LL | pub mod inner;
+ | ^
+ |
+ = help: move `bad.rs` to `bad/mod.rs`
++ = note: `-D clippy::self-named-module-files` implied by `-D warnings`
+
+error: aborting due to previous error
+
--- /dev/null
- = note: `-D clippy::mod-module-files` implied by `-D warnings`
+error: `mod.rs` files are not allowed, found `bad/mod.rs`
+ --> $DIR/bad/mod.rs:1:1
+ |
+LL | pub struct Thing;
+ | ^
+ |
+ = help: move `bad/mod.rs` to `bad.rs`
++ = note: `-D clippy::mod-module-files` implied by `-D warnings`
+
+error: aborting due to previous error
+
--- /dev/null
--- /dev/null
++pub static OPTION: [&str; 3] = ["core", "option", "Option"];
++pub const RESULT: &[&str] = &["core", "result", "Result"];
--- /dev/null
- = help: please use a valid semantic version, see `doc/adding_lints.md`
+error: this item has an invalid `clippy::version` attribute
+ --> $DIR/check_clippy_version_attribute.rs:40:1
+ |
+LL | / declare_tool_lint! {
+LL | | #[clippy::version = "1.2.3.4.5.6"]
+LL | | pub clippy::INVALID_ONE,
+LL | | Warn,
+LL | | "One",
+LL | | report_in_external_macro: true
+LL | | }
+ | |_^
+ |
++ = help: please use a valid semantic version, see `doc/adding_lints.md`
+note: the lint level is defined here
+ --> $DIR/check_clippy_version_attribute.rs:1:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::invalid_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
- = note: `#[deny(clippy::missing_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
+ = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this item has an invalid `clippy::version` attribute
+ --> $DIR/check_clippy_version_attribute.rs:48:1
+ |
+LL | / declare_tool_lint! {
+LL | | #[clippy::version = "I'm a string"]
+LL | | pub clippy::INVALID_TWO,
+LL | | Warn,
+LL | | "Two",
+LL | | report_in_external_macro: true
+LL | | }
+ | |_^
+ |
+ = help: please use a valid semantic version, see `doc/adding_lints.md`
+ = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this lint is missing the `clippy::version` attribute or version value
+ --> $DIR/check_clippy_version_attribute.rs:59:1
+ |
+LL | / declare_tool_lint! {
+LL | | #[clippy::version]
+LL | | pub clippy::MISSING_ATTRIBUTE_ONE,
+LL | | Warn,
+LL | | "Two",
+LL | | report_in_external_macro: true
+LL | | }
+ | |_^
+ |
+ = help: please use a `clippy::version` attribute, see `doc/adding_lints.md`
++ = note: `#[deny(clippy::missing_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
+ = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this lint is missing the `clippy::version` attribute or version value
+ --> $DIR/check_clippy_version_attribute.rs:67:1
+ |
+LL | / declare_tool_lint! {
+LL | | pub clippy::MISSING_ATTRIBUTE_TWO,
+LL | | Warn,
+LL | | "Two",
+LL | | report_in_external_macro: true
+LL | | }
+ | |_^
+ |
+ = help: please use a `clippy::version` attribute, see `doc/adding_lints.md`
+ = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 4 previous errors
+
--- /dev/null
- = note: `-D clippy::if-chain-style` implied by `-D warnings`
+error: this `if` can be part of the inner `if_chain!`
+ --> $DIR/if_chain_style.rs:9:5
+ |
+LL | / if true {
+LL | | let x = "";
+LL | | // `if_chain!` inside `if`
+LL | | if_chain! {
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
+help: this `let` statement can also be in the `if_chain!`
+ --> $DIR/if_chain_style.rs:10:9
+ |
+LL | let x = "";
+ | ^^^^^^^^^^^
++ = note: `-D clippy::if-chain-style` implied by `-D warnings`
+
+error: `if a && b;` should be `if a; if b;`
+ --> $DIR/if_chain_style.rs:19:12
+ |
+LL | if true
+ | ____________^
+LL | | // multi-line AND'ed conditions
+LL | | && false;
+ | |____________________^
+
+error: `let` expression should be inside `then { .. }`
+ --> $DIR/if_chain_style.rs:24:9
+ |
+LL | let x = "";
+ | ^^^^^^^^^^^
+
+error: this `if` can be part of the outer `if_chain!`
+ --> $DIR/if_chain_style.rs:35:13
+ |
+LL | if true {}
+ | ^^^^^^^^^^
+ |
+help: this `let` statement can also be in the `if_chain!`
+ --> $DIR/if_chain_style.rs:33:13
+ |
+LL | let x = "";
+ | ^^^^^^^^^^^
+
+error: `if_chain!` only has one `if`
+ --> $DIR/if_chain_style.rs:29:5
+ |
+LL | / if_chain! {
+LL | | // single `if` condition
+LL | | if true;
+LL | | then {
+... |
+LL | | }
+LL | | }
+ | |_____^
+ |
+ = note: this error originates in the macro `__if_chain` which comes from the expansion of the macro `if_chain` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: `let` expression should be above the `if_chain!`
+ --> $DIR/if_chain_style.rs:40:9
+ |
+LL | let x = "";
+ | ^^^^^^^^^^^
+
+error: this `if_chain!` can be merged with the outer `if_chain!`
+ --> $DIR/if_chain_style.rs:46:13
+ |
+LL | / if_chain! {
+LL | | if true;
+LL | | if true;
+LL | | then {}
+LL | | }
+ | |_____________^
+ |
+help: these `let` statements can also be in the `if_chain!`
+ --> $DIR/if_chain_style.rs:43:13
+ |
+LL | / let x = "";
+LL | | let x = "";
+ | |_______________________^
+
+error: aborting due to 7 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++// aux-build:paths.rs
++#![deny(clippy::internal)]
++#![feature(rustc_private)]
++
++extern crate clippy_utils;
++extern crate paths;
++extern crate rustc_hir;
++extern crate rustc_lint;
++extern crate rustc_middle;
++extern crate rustc_span;
++
++#[allow(unused)]
++use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item, match_type};
++#[allow(unused)]
++use clippy_utils::{
++ is_expr_path_def_path, is_path_diagnostic_item, is_res_diagnostic_ctor, is_res_lang_ctor, is_trait_method,
++ match_def_path, match_trait_method, path_res,
++};
++
++#[allow(unused)]
++use rustc_hir::LangItem;
++#[allow(unused)]
++use rustc_span::sym;
++
++use rustc_hir::def_id::DefId;
++use rustc_hir::Expr;
++use rustc_lint::LateContext;
++use rustc_middle::ty::Ty;
++
++#[allow(unused)]
++static OPTION: [&str; 3] = ["core", "option", "Option"];
++#[allow(unused)]
++const RESULT: &[&str] = &["core", "result", "Result"];
++
++fn _f<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, did: DefId, expr: &Expr<'_>) {
++ let _ = is_type_diagnostic_item(cx, ty, sym::Option);
++ let _ = is_type_diagnostic_item(cx, ty, sym::Result);
++ let _ = is_type_diagnostic_item(cx, ty, sym::Result);
++
++ #[allow(unused)]
++ let rc_path = &["alloc", "rc", "Rc"];
++ let _ = is_type_diagnostic_item(cx, ty, sym::Rc);
++
++ let _ = is_type_diagnostic_item(cx, ty, sym::Option);
++ let _ = is_type_diagnostic_item(cx, ty, sym::Result);
++
++ let _ = is_type_lang_item(cx, ty, LangItem::OwnedBox);
++ let _ = is_type_diagnostic_item(cx, ty, sym::maybe_uninit_uninit);
++
++ let _ = cx.tcx.lang_items().require(LangItem::OwnedBox).ok() == Some(did);
++ let _ = cx.tcx.is_diagnostic_item(sym::Option, did);
++ let _ = cx.tcx.lang_items().require(LangItem::OptionSome).ok() == Some(did);
++
++ let _ = is_trait_method(cx, expr, sym::AsRef);
++
++ let _ = is_path_diagnostic_item(cx, expr, sym::Option);
++ let _ = path_res(cx, expr).opt_def_id().map_or(false, |id| cx.tcx.lang_items().require(LangItem::IteratorNext).ok() == Some(id));
++ let _ = is_res_lang_ctor(cx, path_res(cx, expr), LangItem::OptionSome);
++}
++
++fn main() {}
--- /dev/null
--- /dev/null
++// run-rustfix
++// aux-build:paths.rs
++#![deny(clippy::internal)]
++#![feature(rustc_private)]
++
++extern crate clippy_utils;
++extern crate paths;
++extern crate rustc_hir;
++extern crate rustc_lint;
++extern crate rustc_middle;
++extern crate rustc_span;
++
++#[allow(unused)]
++use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item, match_type};
++#[allow(unused)]
++use clippy_utils::{
++ is_expr_path_def_path, is_path_diagnostic_item, is_res_diagnostic_ctor, is_res_lang_ctor, is_trait_method,
++ match_def_path, match_trait_method, path_res,
++};
++
++#[allow(unused)]
++use rustc_hir::LangItem;
++#[allow(unused)]
++use rustc_span::sym;
++
++use rustc_hir::def_id::DefId;
++use rustc_hir::Expr;
++use rustc_lint::LateContext;
++use rustc_middle::ty::Ty;
++
++#[allow(unused)]
++static OPTION: [&str; 3] = ["core", "option", "Option"];
++#[allow(unused)]
++const RESULT: &[&str] = &["core", "result", "Result"];
++
++fn _f<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, did: DefId, expr: &Expr<'_>) {
++ let _ = match_type(cx, ty, &OPTION);
++ let _ = match_type(cx, ty, RESULT);
++ let _ = match_type(cx, ty, &["core", "result", "Result"]);
++
++ #[allow(unused)]
++ let rc_path = &["alloc", "rc", "Rc"];
++ let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
++
++ let _ = match_type(cx, ty, &paths::OPTION);
++ let _ = match_type(cx, ty, paths::RESULT);
++
++ let _ = match_type(cx, ty, &["alloc", "boxed", "Box"]);
++ let _ = match_type(cx, ty, &["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"]);
++
++ let _ = match_def_path(cx, did, &["alloc", "boxed", "Box"]);
++ let _ = match_def_path(cx, did, &["core", "option", "Option"]);
++ let _ = match_def_path(cx, did, &["core", "option", "Option", "Some"]);
++
++ let _ = match_trait_method(cx, expr, &["core", "convert", "AsRef"]);
++
++ let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option"]);
++ let _ = is_expr_path_def_path(cx, expr, &["core", "iter", "traits", "Iterator", "next"]);
++ let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option", "Some"]);
++}
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:37:13
++ |
++LL | let _ = match_type(cx, ty, &OPTION);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Option)`
++ |
++note: the lint level is defined here
++ --> $DIR/unnecessary_def_path.rs:3:9
++ |
++LL | #![deny(clippy::internal)]
++ | ^^^^^^^^^^^^^^^^
++ = note: `#[deny(clippy::unnecessary_def_path)]` implied by `#[deny(clippy::internal)]`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:38:13
++ |
++LL | let _ = match_type(cx, ty, RESULT);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Result)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:39:13
++ |
++LL | let _ = match_type(cx, ty, &["core", "result", "Result"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Result)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:43:13
++ |
++LL | let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Rc)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:45:13
++ |
++LL | let _ = match_type(cx, ty, &paths::OPTION);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Option)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:46:13
++ |
++LL | let _ = match_type(cx, ty, paths::RESULT);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::Result)`
++
++error: use of a def path to a `LangItem`
++ --> $DIR/unnecessary_def_path.rs:48:13
++ |
++LL | let _ = match_type(cx, ty, &["alloc", "boxed", "Box"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_lang_item(cx, ty, LangItem::OwnedBox)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:49:13
++ |
++LL | let _ = match_type(cx, ty, &["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_type_diagnostic_item(cx, ty, sym::maybe_uninit_uninit)`
++
++error: use of a def path to a `LangItem`
++ --> $DIR/unnecessary_def_path.rs:51:13
++ |
++LL | let _ = match_def_path(cx, did, &["alloc", "boxed", "Box"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cx.tcx.lang_items().require(LangItem::OwnedBox).ok() == Some(did)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:52:13
++ |
++LL | let _ = match_def_path(cx, did, &["core", "option", "Option"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cx.tcx.is_diagnostic_item(sym::Option, did)`
++
++error: use of a def path to a `LangItem`
++ --> $DIR/unnecessary_def_path.rs:53:13
++ |
++LL | let _ = match_def_path(cx, did, &["core", "option", "Option", "Some"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cx.tcx.lang_items().require(LangItem::OptionSome).ok() == Some(did)`
++ |
++ = help: if this `DefId` came from a constructor expression or pattern then the parent `DefId` should be used instead
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:55:13
++ |
++LL | let _ = match_trait_method(cx, expr, &["core", "convert", "AsRef"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_trait_method(cx, expr, sym::AsRef)`
++
++error: use of a def path to a diagnostic item
++ --> $DIR/unnecessary_def_path.rs:57:13
++ |
++LL | let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_path_diagnostic_item(cx, expr, sym::Option)`
++
++error: use of a def path to a `LangItem`
++ --> $DIR/unnecessary_def_path.rs:58:13
++ |
++LL | let _ = is_expr_path_def_path(cx, expr, &["core", "iter", "traits", "Iterator", "next"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `path_res(cx, expr).opt_def_id().map_or(false, |id| cx.tcx.lang_items().require(LangItem::IteratorNext).ok() == Some(id))`
++
++error: use of a def path to a `LangItem`
++ --> $DIR/unnecessary_def_path.rs:59:13
++ |
++LL | let _ = is_expr_path_def_path(cx, expr, &["core", "option", "Option", "Some"]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `is_res_lang_ctor(cx, path_res(cx, expr), LangItem::OptionSome)`
++
++error: aborting due to 15 previous errors
++
--- /dev/null
++#![allow(clippy::uninlined_format_args)]
++
+fn main() {}
+
+#[warn(clippy::cognitive_complexity)]
+fn cognitive_complexity() {
+ let x = vec![1, 2, 3];
+ for i in x {
+ if i == 1 {
+ println!("{}", i);
+ }
+ }
+}
--- /dev/null
- --> $DIR/conf_deprecated_key.rs:4:4
+warning: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead
+
+warning: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `blacklisted-names`. Please use `disallowed-names` instead
+
+error: the function has a cognitive complexity of (3/2)
++ --> $DIR/conf_deprecated_key.rs:6:4
+ |
+LL | fn cognitive_complexity() {
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: you could split it up into multiple smaller functions
+ = note: `-D clippy::cognitive-complexity` implied by `-D warnings`
+
+error: aborting due to previous error; 2 warnings emitted
+
--- /dev/null
--- /dev/null
++#[macro_export]
++macro_rules! expr {
++ () => {
++ 1
++ };
++}
++
++#[macro_export]
++macro_rules! stmt {
++ () => {
++ let _x = ();
++ };
++}
++
++#[macro_export]
++macro_rules! ty {
++ () => { &'static str };
++}
++
++#[macro_export]
++macro_rules! pat {
++ () => {
++ _
++ };
++}
++
++#[macro_export]
++macro_rules! item {
++ () => {
++ const ITEM: usize = 1;
++ };
++}
--- /dev/null
--- /dev/null
++disallowed-macros = [
++ "std::println",
++ "std::vec",
++ { path = "std::cfg" },
++ { path = "serde::Serialize", reason = "no serializing" },
++ "macros::expr",
++ "macros::stmt",
++ "macros::ty",
++ "macros::pat",
++ "macros::item",
++]
--- /dev/null
--- /dev/null
++// aux-build:macros.rs
++
++#![allow(unused)]
++
++extern crate macros;
++
++use serde::Serialize;
++
++fn main() {
++ println!("one");
++ println!("two");
++ cfg!(unix);
++ vec![1, 2, 3];
++
++ #[derive(Serialize)]
++ struct Derive;
++
++ let _ = macros::expr!();
++ macros::stmt!();
++ let macros::pat!() = 1;
++ let _: macros::ty!() = "";
++ macros::item!();
++
++ eprintln!("allowed");
++}
++
++struct S;
++
++impl S {
++ macros::item!();
++}
++
++trait Y {
++ macros::item!();
++}
++
++impl Y for S {
++ macros::item!();
++}
--- /dev/null
--- /dev/null
++error: use of a disallowed macro `std::println`
++ --> $DIR/disallowed_macros.rs:10:5
++ |
++LL | println!("one");
++ | ^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::disallowed-macros` implied by `-D warnings`
++
++error: use of a disallowed macro `std::println`
++ --> $DIR/disallowed_macros.rs:11:5
++ |
++LL | println!("two");
++ | ^^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `std::cfg`
++ --> $DIR/disallowed_macros.rs:12:5
++ |
++LL | cfg!(unix);
++ | ^^^^^^^^^^
++
++error: use of a disallowed macro `std::vec`
++ --> $DIR/disallowed_macros.rs:13:5
++ |
++LL | vec![1, 2, 3];
++ | ^^^^^^^^^^^^^
++
++error: use of a disallowed macro `serde::Serialize`
++ --> $DIR/disallowed_macros.rs:15:14
++ |
++LL | #[derive(Serialize)]
++ | ^^^^^^^^^
++ |
++ = note: no serializing (from clippy.toml)
++
++error: use of a disallowed macro `macros::expr`
++ --> $DIR/disallowed_macros.rs:18:13
++ |
++LL | let _ = macros::expr!();
++ | ^^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::stmt`
++ --> $DIR/disallowed_macros.rs:19:5
++ |
++LL | macros::stmt!();
++ | ^^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::pat`
++ --> $DIR/disallowed_macros.rs:20:9
++ |
++LL | let macros::pat!() = 1;
++ | ^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::ty`
++ --> $DIR/disallowed_macros.rs:21:12
++ |
++LL | let _: macros::ty!() = "";
++ | ^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::item`
++ --> $DIR/disallowed_macros.rs:22:5
++ |
++LL | macros::item!();
++ | ^^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::item`
++ --> $DIR/disallowed_macros.rs:30:5
++ |
++LL | macros::item!();
++ | ^^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::item`
++ --> $DIR/disallowed_macros.rs:34:5
++ |
++LL | macros::item!();
++ | ^^^^^^^^^^^^^^^
++
++error: use of a disallowed macro `macros::item`
++ --> $DIR/disallowed_macros.rs:38:5
++ |
++LL | macros::item!();
++ | ^^^^^^^^^^^^^^^
++
++error: aborting due to 13 previous errors
++
--- /dev/null
--- /dev/null
++// aux-build:proc_macro_derive.rs
++// run-rustfix
++
++#![warn(clippy::nonstandard_macro_braces)]
++
++extern crate proc_macro_derive;
++extern crate quote;
++
++use quote::quote;
++
++#[derive(proc_macro_derive::DeriveSomething)]
++pub struct S;
++
++proc_macro_derive::foo_bar!();
++
++#[rustfmt::skip]
++macro_rules! test {
++ () => {
++ vec![0, 0, 0]
++ };
++}
++
++#[rustfmt::skip]
++macro_rules! test2 {
++ ($($arg:tt)*) => {
++ format_args!($($arg)*)
++ };
++}
++
++macro_rules! type_pos {
++ ($what:ty) => {
++ Vec<$what>
++ };
++}
++
++macro_rules! printlnfoo {
++ ($thing:expr) => {
++ println!("{}", $thing)
++ };
++}
++
++#[rustfmt::skip]
++fn main() {
++ let _ = vec![1, 2, 3];
++ let _ = format!("ugh {} stop being such a good compiler", "hello");
++ let _ = matches!({}, ());
++ let _ = quote!{let x = 1;};
++ let _ = quote::quote!{match match match};
++ let _ = test!(); // trigger when macro def is inside our own crate
++ let _ = vec![1,2,3];
++
++ let _ = quote::quote! {true || false};
++ let _ = vec! [0 ,0 ,0];
++ let _ = format!("fds{}fds", 10);
++ let _ = test2!["{}{}{}", 1, 2, 3];
++
++ let _: type_pos![usize] = vec![];
++
++ eprint!["test if user config overrides defaults"];
++
++ printlnfoo!["test if printlnfoo is triggered by println"];
++}
--- /dev/null
+// aux-build:proc_macro_derive.rs
++// run-rustfix
+
+#![warn(clippy::nonstandard_macro_braces)]
+
+extern crate proc_macro_derive;
+extern crate quote;
+
+use quote::quote;
+
+#[derive(proc_macro_derive::DeriveSomething)]
+pub struct S;
+
+proc_macro_derive::foo_bar!();
+
+#[rustfmt::skip]
+macro_rules! test {
+ () => {
+ vec!{0, 0, 0}
+ };
+}
+
+#[rustfmt::skip]
+macro_rules! test2 {
+ ($($arg:tt)*) => {
+ format_args!($($arg)*)
+ };
+}
+
+macro_rules! type_pos {
+ ($what:ty) => {
+ Vec<$what>
+ };
+}
+
+macro_rules! printlnfoo {
+ ($thing:expr) => {
+ println!("{}", $thing)
+ };
+}
+
+#[rustfmt::skip]
+fn main() {
+ let _ = vec! {1, 2, 3};
+ let _ = format!["ugh {} stop being such a good compiler", "hello"];
+ let _ = matches!{{}, ()};
+ let _ = quote!(let x = 1;);
+ let _ = quote::quote!(match match match);
+ let _ = test!(); // trigger when macro def is inside our own crate
+ let _ = vec![1,2,3];
+
+ let _ = quote::quote! {true || false};
+ let _ = vec! [0 ,0 ,0];
+ let _ = format!("fds{}fds", 10);
+ let _ = test2!["{}{}{}", 1, 2, 3];
+
+ let _: type_pos!(usize) = vec![];
+
+ eprint!("test if user config overrides defaults");
+
+ printlnfoo!["test if printlnfoo is triggered by println"];
+}
--- /dev/null
- --> $DIR/conf_nonstandard_macro_braces.rs:43:13
+error: use of irregular braces for `vec!` macro
- | ^^^^^^^^^^^^^^
- |
- help: consider writing `vec![1, 2, 3]`
- --> $DIR/conf_nonstandard_macro_braces.rs:43:13
++ --> $DIR/conf_nonstandard_macro_braces.rs:44:13
+ |
+LL | let _ = vec! {1, 2, 3};
- LL | let _ = vec! {1, 2, 3};
- | ^^^^^^^^^^^^^^
++ | ^^^^^^^^^^^^^^ help: consider writing: `vec![1, 2, 3]`
+ |
- --> $DIR/conf_nonstandard_macro_braces.rs:44:13
- |
- LL | let _ = format!["ugh {} stop being such a good compiler", "hello"];
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- |
- help: consider writing `format!("ugh () stop being such a good compiler", "hello")`
- --> $DIR/conf_nonstandard_macro_braces.rs:44:13
+ = note: `-D clippy::nonstandard-macro-braces` implied by `-D warnings`
+
+error: use of irregular braces for `format!` macro
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:45:13
+ |
+LL | let _ = format!["ugh {} stop being such a good compiler", "hello"];
- --> $DIR/conf_nonstandard_macro_braces.rs:45:13
- |
- LL | let _ = matches!{{}, ()};
- | ^^^^^^^^^^^^^^^^
- |
- help: consider writing `matches!((), ())`
- --> $DIR/conf_nonstandard_macro_braces.rs:45:13
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `format!("ugh {} stop being such a good compiler", "hello")`
+
+error: use of irregular braces for `matches!` macro
- | ^^^^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:46:13
+ |
+LL | let _ = matches!{{}, ()};
- --> $DIR/conf_nonstandard_macro_braces.rs:46:13
- |
- LL | let _ = quote!(let x = 1;);
- | ^^^^^^^^^^^^^^^^^^
- |
- help: consider writing `quote! {let x = 1;}`
- --> $DIR/conf_nonstandard_macro_braces.rs:46:13
++ | ^^^^^^^^^^^^^^^^ help: consider writing: `matches!({}, ())`
+
+error: use of irregular braces for `quote!` macro
- | ^^^^^^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:47:13
+ |
+LL | let _ = quote!(let x = 1;);
- --> $DIR/conf_nonstandard_macro_braces.rs:47:13
++ | ^^^^^^^^^^^^^^^^^^ help: consider writing: `quote!{let x = 1;}`
+
+error: use of irregular braces for `quote::quote!` macro
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- |
- help: consider writing `quote::quote! {match match match}`
- --> $DIR/conf_nonstandard_macro_braces.rs:47:13
- |
- LL | let _ = quote::quote!(match match match);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:48:13
+ |
+LL | let _ = quote::quote!(match match match);
- --> $DIR/conf_nonstandard_macro_braces.rs:18:9
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `quote::quote!{match match match}`
+
+error: use of irregular braces for `vec!` macro
- | ^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:19:9
+ |
+LL | vec!{0, 0, 0}
- help: consider writing `vec![0, 0, 0]`
- --> $DIR/conf_nonstandard_macro_braces.rs:18:9
- |
- LL | vec!{0, 0, 0}
- | ^^^^^^^^^^^^^
- ...
- LL | let _ = test!(); // trigger when macro def is inside our own crate
- | ------- in this macro invocation
++ | ^^^^^^^^^^^^^ help: consider writing: `vec![0, 0, 0]`
+...
+LL | let _ = test!(); // trigger when macro def is inside our own crate
+ | ------- in this macro invocation
+ |
- --> $DIR/conf_nonstandard_macro_braces.rs:56:12
+ = note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: use of irregular braces for `type_pos!` macro
- | ^^^^^^^^^^^^^^^^
- |
- help: consider writing `type_pos![usize]`
- --> $DIR/conf_nonstandard_macro_braces.rs:56:12
- |
- LL | let _: type_pos!(usize) = vec![];
- | ^^^^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:57:12
+ |
+LL | let _: type_pos!(usize) = vec![];
- --> $DIR/conf_nonstandard_macro_braces.rs:58:5
- |
- LL | eprint!("test if user config overrides defaults");
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- |
- help: consider writing `eprint!["test if user config overrides defaults"]`
- --> $DIR/conf_nonstandard_macro_braces.rs:58:5
++ | ^^^^^^^^^^^^^^^^ help: consider writing: `type_pos![usize]`
+
+error: use of irregular braces for `eprint!` macro
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/conf_nonstandard_macro_braces.rs:59:5
+ |
+LL | eprint!("test if user config overrides defaults");
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `eprint!["test if user config overrides defaults"]`
+
+error: aborting due to 8 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" },
+]
--- /dev/null
+#![warn(clippy::disallowed_methods)]
+
++extern crate futures;
+extern crate regex;
++
++use futures::stream::{empty, select_all};
+use regex::Regex;
+
+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::<()>()]);
+}
--- /dev/null
- --> $DIR/conf_disallowed_methods.rs:7:14
+error: use of a disallowed method `regex::Regex::new`
- --> $DIR/conf_disallowed_methods.rs:8:5
++ --> $DIR/conf_disallowed_methods.rs:10: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:11:5
++ --> $DIR/conf_disallowed_methods.rs:11: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:13:5
++ --> $DIR/conf_disallowed_methods.rs:14:5
+ |
+LL | a.iter().sum::<i32>();
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `slice::sort_unstable`
- --> $DIR/conf_disallowed_methods.rs:15:13
++ --> $DIR/conf_disallowed_methods.rs:16:5
+ |
+LL | a.sort_unstable();
+ | ^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `f32::clamp`
- --> $DIR/conf_disallowed_methods.rs:18:61
++ --> $DIR/conf_disallowed_methods.rs:18:13
+ |
+LL | let _ = 2.0f32.clamp(3.0f32, 4.0f32);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: use of a disallowed method `regex::Regex::new`
- --> $DIR/conf_disallowed_methods.rs:21:28
++ --> $DIR/conf_disallowed_methods.rs:21: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:22:53
++ --> $DIR/conf_disallowed_methods.rs:24:28
+ |
+LL | let in_call = Box::new(f32::clamp);
+ | ^^^^^^^^^^
+
+error: use of a disallowed method `regex::Regex::new`
- error: aborting due to 8 previous errors
++ --> $DIR/conf_disallowed_methods.rs:25:53
+ |
+LL | let in_method_call = ["^", "$"].into_iter().map(Regex::new);
+ | ^^^^^^^^^^
+
++error: use of a disallowed method `futures::stream::select_all`
++ --> $DIR/conf_disallowed_methods.rs:28:31
++ |
++LL | let same_name_as_module = select_all(vec![empty::<()>()]);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: aborting due to 9 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-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
+ large-error-threshold
+ literal-representation-threshold
+ 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
- #![feature(inline_const, saturating_int_impl)]
+#![allow(
+ clippy::assign_op_pattern,
+ clippy::erasing_op,
+ clippy::identity_op,
++ clippy::op_ref,
+ clippy::unnecessary_owned_empty_strings,
+ arithmetic_overflow,
+ unconditional_panic
+)]
- const _: i32 = { let mut n = -1; n = -(-1); n = -n; n };
- let _ = const { let mut n = -1; n = -(-1); n = -n; n };
++#![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 };
+
- pub fn non_overflowing_runtime_ops_or_ops_already_handled_by_the_compiler() {
++ 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 overflowing_runtime_ops() {
++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 = -1;
+ _n = -(-1);
+}
+
++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:119:5
+error: arithmetic operation that can potentially result in unexpected side-effects
- = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings`
++ --> $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:161:5
+ |
+LL | _n += 1;
+ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:162:5
+ |
- --> $DIR/arithmetic_side_effects.rs:120:5
++LL | _n += &1;
++ | ^^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:121:5
++ --> $DIR/arithmetic_side_effects.rs:163:5
+ |
+LL | _n -= 1;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:122:5
++ --> $DIR/arithmetic_side_effects.rs:164:5
++ |
++LL | _n -= &1;
++ | ^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:165:5
+ |
+LL | _n /= 0;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:123:5
++ --> $DIR/arithmetic_side_effects.rs:166:5
++ |
++LL | _n /= &0;
++ | ^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:167:5
+ |
+LL | _n %= 0;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:126:10
++ --> $DIR/arithmetic_side_effects.rs:168:5
++ |
++LL | _n %= &0;
++ | ^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:169:5
+ |
+LL | _n *= 2;
+ | ^^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:127:10
++ --> $DIR/arithmetic_side_effects.rs:170:5
++ |
++LL | _n *= &2;
++ | ^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:173:10
+ |
+LL | _n = _n + 1;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:128:10
++ --> $DIR/arithmetic_side_effects.rs:174:10
++ |
++LL | _n = _n + &1;
++ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:175:10
+ |
+LL | _n = 1 + _n;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:129:10
++ --> $DIR/arithmetic_side_effects.rs:176:10
++ |
++LL | _n = &1 + _n;
++ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $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:130: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:179:10
+ |
+LL | _n = 1 - _n;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:131: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:181:10
+ |
+LL | _n = _n / 0;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:132:10
++ --> $DIR/arithmetic_side_effects.rs:182:10
++ |
++LL | _n = _n / &0;
++ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:183:10
+ |
+LL | _n = _n % 0;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:133:10
++ --> $DIR/arithmetic_side_effects.rs:184:10
++ |
++LL | _n = _n % &0;
++ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:185:10
+ |
+LL | _n = _n * 2;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- --> $DIR/arithmetic_side_effects.rs:136:10
++ --> $DIR/arithmetic_side_effects.rs:186:10
++ |
++LL | _n = _n * &2;
++ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:187:10
+ |
+LL | _n = 2 * _n;
+ | ^^^^^^
+
+error: arithmetic operation that can potentially result in unexpected side-effects
- error: aborting due to 14 previous errors
++ --> $DIR/arithmetic_side_effects.rs:188:10
++ |
++LL | _n = &2 * _n;
++ | ^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:189:10
++ |
++LL | _n = 23 + &85;
++ | ^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:190:10
++ |
++LL | _n = &23 + 85;
++ | ^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:191:10
++ |
++LL | _n = &23 + &85;
++ | ^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:194:13
++ |
++LL | let _ = Custom + 0;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:195:13
++ |
++LL | let _ = Custom + 1;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:196:13
++ |
++LL | let _ = Custom + 2;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:197:13
++ |
++LL | let _ = Custom + 0.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:198:13
++ |
++LL | let _ = Custom + 1.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:199:13
++ |
++LL | let _ = Custom + 2.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:200:13
++ |
++LL | let _ = Custom - 0;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:201:13
++ |
++LL | let _ = Custom - 1;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:202:13
++ |
++LL | let _ = Custom - 2;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:203:13
++ |
++LL | let _ = Custom - 0.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:204:13
++ |
++LL | let _ = Custom - 1.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:205:13
++ |
++LL | let _ = Custom - 2.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:206:13
++ |
++LL | let _ = Custom / 0;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:207:13
++ |
++LL | let _ = Custom / 1;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:208:13
++ |
++LL | let _ = Custom / 2;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:209:13
++ |
++LL | let _ = Custom / 0.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:210:13
++ |
++LL | let _ = Custom / 1.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:211:13
++ |
++LL | let _ = Custom / 2.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:212:13
++ |
++LL | let _ = Custom * 0;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:213:13
++ |
++LL | let _ = Custom * 1;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:214:13
++ |
++LL | let _ = Custom * 2;
++ | ^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:215:13
++ |
++LL | let _ = Custom * 0.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:216:13
++ |
++LL | let _ = Custom * 1.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:217:13
++ |
++LL | let _ = Custom * 2.0;
++ | ^^^^^^^^^^^^
++
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:220:10
+ |
+LL | _n = -_n;
+ | ^^^
+
++error: arithmetic operation that can potentially result in unexpected side-effects
++ --> $DIR/arithmetic_side_effects.rs:221:10
++ |
++LL | _n = -&_n;
++ | ^^^^
++
++error: aborting due to 58 previous errors
+
--- /dev/null
++#![allow(clippy::uninlined_format_args)]
++
+#[allow(unused_assignments)]
+#[warn(clippy::misrefactored_assign_op, clippy::assign_op_pattern)]
+fn main() {
+ let mut a = 5;
+ a += a + 1;
+ a += 1 + a;
+ a -= a - 1;
+ a *= a * 99;
+ a *= 42 * a;
+ a /= a / 2;
+ a %= a % 5;
+ a &= a & 1;
+ a *= a * a;
+ a = a * a * a;
+ a = a * 42 * a;
+ a = a * 2 + a;
+ a -= 1 - a;
+ a /= 5 / a;
+ a %= 42 % a;
+ a <<= 6 << a;
+}
+
+// check that we don't lint on op assign impls, because that's just the way to impl them
+
+use std::ops::{Mul, MulAssign};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct Wrap(i64);
+
+impl Mul<i64> for Wrap {
+ type Output = Self;
+
+ fn mul(self, rhs: i64) -> Self {
+ Wrap(self.0 * rhs)
+ }
+}
+
+impl MulAssign<i64> for Wrap {
+ fn mul_assign(&mut self, rhs: i64) {
+ *self = *self * rhs
+ }
+}
+
+fn cow_add_assign() {
+ use std::borrow::Cow;
+ let mut buf = Cow::Owned(String::from("bar"));
+ let cows = Cow::Borrowed("foo");
+
+ // this can be linted
+ buf = buf + cows.clone();
+
+ // this should not as cow<str> Add is not commutative
+ buf = cows + buf;
+ println!("{}", buf);
+}
--- /dev/null
- --> $DIR/assign_ops2.rs:5:5
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:6:5
++ --> $DIR/assign_ops2.rs:7:5
+ |
+LL | a += a + 1;
+ | ^^^^^^^^^^
+ |
+ = note: `-D clippy::misrefactored-assign-op` implied by `-D warnings`
+help: did you mean `a = a + 1` or `a = a + a + 1`? Consider replacing it with
+ |
+LL | a += 1;
+ | ~~~~~~
+help: or
+ |
+LL | a = a + a + 1;
+ | ~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:7:5
++ --> $DIR/assign_ops2.rs:8:5
+ |
+LL | a += 1 + a;
+ | ^^^^^^^^^^
+ |
+help: did you mean `a = a + 1` or `a = a + 1 + a`? Consider replacing it with
+ |
+LL | a += 1;
+ | ~~~~~~
+help: or
+ |
+LL | a = a + 1 + a;
+ | ~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:8:5
++ --> $DIR/assign_ops2.rs:9:5
+ |
+LL | a -= a - 1;
+ | ^^^^^^^^^^
+ |
+help: did you mean `a = a - 1` or `a = a - (a - 1)`? Consider replacing it with
+ |
+LL | a -= 1;
+ | ~~~~~~
+help: or
+ |
+LL | a = a - (a - 1);
+ | ~~~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:9:5
++ --> $DIR/assign_ops2.rs:10:5
+ |
+LL | a *= a * 99;
+ | ^^^^^^^^^^^
+ |
+help: did you mean `a = a * 99` or `a = a * a * 99`? Consider replacing it with
+ |
+LL | a *= 99;
+ | ~~~~~~~
+help: or
+ |
+LL | a = a * a * 99;
+ | ~~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:10:5
++ --> $DIR/assign_ops2.rs:11:5
+ |
+LL | a *= 42 * a;
+ | ^^^^^^^^^^^
+ |
+help: did you mean `a = a * 42` or `a = a * 42 * a`? Consider replacing it with
+ |
+LL | a *= 42;
+ | ~~~~~~~
+help: or
+ |
+LL | a = a * 42 * a;
+ | ~~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:11:5
++ --> $DIR/assign_ops2.rs:12:5
+ |
+LL | a /= a / 2;
+ | ^^^^^^^^^^
+ |
+help: did you mean `a = a / 2` or `a = a / (a / 2)`? Consider replacing it with
+ |
+LL | a /= 2;
+ | ~~~~~~
+help: or
+ |
+LL | a = a / (a / 2);
+ | ~~~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:12:5
++ --> $DIR/assign_ops2.rs:13:5
+ |
+LL | a %= a % 5;
+ | ^^^^^^^^^^
+ |
+help: did you mean `a = a % 5` or `a = a % (a % 5)`? Consider replacing it with
+ |
+LL | a %= 5;
+ | ~~~~~~
+help: or
+ |
+LL | a = a % (a % 5);
+ | ~~~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:13:5
++ --> $DIR/assign_ops2.rs:14:5
+ |
+LL | a &= a & 1;
+ | ^^^^^^^^^^
+ |
+help: did you mean `a = a & 1` or `a = a & a & 1`? Consider replacing it with
+ |
+LL | a &= 1;
+ | ~~~~~~
+help: or
+ |
+LL | a = a & a & 1;
+ | ~~~~~~~~~~~~~
+
+error: variable appears on both sides of an assignment operation
- --> $DIR/assign_ops2.rs:50:5
++ --> $DIR/assign_ops2.rs:15:5
+ |
+LL | a *= a * a;
+ | ^^^^^^^^^^
+ |
+help: did you mean `a = a * a` or `a = a * a * a`? Consider replacing it with
+ |
+LL | a *= a;
+ | ~~~~~~
+help: or
+ |
+LL | a = a * a * a;
+ | ~~~~~~~~~~~~~
+
+error: manual implementation of an assign operation
++ --> $DIR/assign_ops2.rs:52:5
+ |
+LL | buf = buf + cows.clone();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `buf += cows.clone()`
+ |
+ = note: `-D clippy::assign-op-pattern` implied by `-D warnings`
+
+error: aborting due to 10 previous errors
+
--- /dev/null
- #![allow(clippy::useless_conversion)]
+// compile-flags: --emit=link
+// no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+#![feature(repr128, proc_macro_hygiene, proc_macro_quote, box_patterns)]
+#![allow(incomplete_features)]
++#![allow(clippy::useless_conversion, clippy::uninlined_format_args)]
+
+extern crate proc_macro;
+extern crate quote;
+extern crate syn;
+
+use proc_macro::TokenStream;
+use quote::{quote, quote_spanned};
+use syn::parse_macro_input;
+use syn::spanned::Spanned;
+use syn::token::Star;
+use syn::{
+ parse_quote, FnArg, ImplItem, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, Signature, TraitItem, Type,
+};
+
+#[proc_macro_attribute]
+pub fn dummy(_args: TokenStream, input: TokenStream) -> TokenStream {
+ input
+}
+
+#[proc_macro_attribute]
+pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream {
+ let mut item = parse_macro_input!(input as ItemTrait);
+ for inner in &mut item.items {
+ if let TraitItem::Method(method) = inner {
+ let sig = &method.sig;
+ let block = &mut method.default;
+ if let Some(block) = block {
+ let brace = block.brace_token;
+
+ let my_block = quote_spanned!( brace.span => {
+ // Should not trigger `empty_line_after_outer_attr`
+ #[crate_type = "lib"]
+ #sig #block
+ Vec::new()
+ });
+ *block = parse_quote!(#my_block);
+ }
+ }
+ }
+ TokenStream::from(quote!(#item))
+}
+
+#[proc_macro_attribute]
+pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStream {
+ fn make_name(count: usize) -> String {
+ format!("'life{}", count)
+ }
+
+ fn mut_receiver_of(sig: &mut Signature) -> Option<&mut FnArg> {
+ let arg = sig.inputs.first_mut()?;
+ if let FnArg::Typed(PatType { pat, .. }) = arg {
+ if let Pat::Ident(PatIdent { ident, .. }) = &**pat {
+ if ident == "self" {
+ return Some(arg);
+ }
+ }
+ }
+ None
+ }
+
+ let mut elided = 0;
+ let mut item = parse_macro_input!(input as ItemImpl);
+
+ // Look for methods having arbitrary self type taken by &mut ref
+ for inner in &mut item.items {
+ if let ImplItem::Method(method) = inner {
+ if let Some(FnArg::Typed(pat_type)) = mut_receiver_of(&mut method.sig) {
+ if let box Type::Reference(reference) = &mut pat_type.ty {
+ // Target only unnamed lifetimes
+ let name = match &reference.lifetime {
+ Some(lt) if lt.ident == "_" => make_name(elided),
+ None => make_name(elided),
+ _ => continue,
+ };
+ elided += 1;
+
+ // HACK: Syn uses `Span` from the proc_macro2 crate, and does not seem to reexport it.
+ // In order to avoid adding the dependency, get a default span from a non-existent token.
+ // A default span is needed to mark the code as coming from expansion.
+ let span = Star::default().span();
+
+ // Replace old lifetime with the named one
+ let lifetime = Lifetime::new(&name, span);
+ reference.lifetime = Some(parse_quote!(#lifetime));
+
+ // Add lifetime to the generics of the method
+ method.sig.generics.params.push(parse_quote!(#lifetime));
+ }
+ }
+ }
+ }
+
+ TokenStream::from(quote!(#item))
+}
--- /dev/null
+// run-rustfix
+#![deny(clippy::bind_instead_of_map)]
++#![allow(clippy::uninlined_format_args)]
+
+// need a main anyway, use it get rid of unused warnings too
+pub fn main() {
+ let x = Some(5);
+ // the easiest cases
+ let _ = x;
+ let _ = x.map(|o| o + 1);
+ // and an easy counter-example
+ let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
+
+ // Different type
+ let x: Result<u32, &str> = Ok(1);
+ let _ = x;
+}
+
+pub fn foo() -> Option<String> {
+ let x = Some(String::from("hello"));
+ Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?)))
+}
+
+pub fn example2(x: bool) -> Option<&'static str> {
+ Some("a").and_then(|s| Some(if x { s } else { return None }))
+}
--- /dev/null
+// run-rustfix
+#![deny(clippy::bind_instead_of_map)]
++#![allow(clippy::uninlined_format_args)]
+
+// need a main anyway, use it get rid of unused warnings too
+pub fn main() {
+ let x = Some(5);
+ // the easiest cases
+ let _ = x.and_then(Some);
+ let _ = x.and_then(|o| Some(o + 1));
+ // and an easy counter-example
+ let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
+
+ // Different type
+ let x: Result<u32, &str> = Ok(1);
+ let _ = x.and_then(Ok);
+}
+
+pub fn foo() -> Option<String> {
+ let x = Some(String::from("hello"));
+ Some("hello".to_owned()).and_then(|s| Some(format!("{}{}", s, x?)))
+}
+
+pub fn example2(x: bool) -> Option<&'static str> {
+ Some("a").and_then(|s| Some(if x { s } else { return None }))
+}
--- /dev/null
- --> $DIR/bind_instead_of_map.rs:8:13
+error: using `Option.and_then(Some)`, which is a no-op
- --> $DIR/bind_instead_of_map.rs:9:13
++ --> $DIR/bind_instead_of_map.rs:9:13
+ |
+LL | let _ = x.and_then(Some);
+ | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x`
+ |
+note: the lint level is defined here
+ --> $DIR/bind_instead_of_map.rs:2:9
+ |
+LL | #![deny(clippy::bind_instead_of_map)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`
- --> $DIR/bind_instead_of_map.rs:15:13
++ --> $DIR/bind_instead_of_map.rs:10:13
+ |
+LL | let _ = x.and_then(|o| Some(o + 1));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `x.map(|o| o + 1)`
+
+error: using `Result.and_then(Ok)`, which is a no-op
++ --> $DIR/bind_instead_of_map.rs:16:13
+ |
+LL | let _ = x.and_then(Ok);
+ | ^^^^^^^^^^^^^^ help: use the expression directly: `x`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- #![allow(clippy::disallowed_names)]
- #![allow(unused_variables)]
- #![allow(dead_code)]
+#![deny(clippy::borrowed_box)]
++#![allow(dead_code, unused_variables)]
++#![allow(clippy::uninlined_format_args, clippy::disallowed_names)]
+
+use std::fmt::Display;
+
+pub fn test1(foo: &mut Box<bool>) {
+ // Although this function could be changed to "&mut bool",
+ // avoiding the Box, mutable references to boxes are not
+ // flagged by this lint.
+ //
+ // This omission is intentional: By passing a mutable Box,
+ // the memory location of the pointed-to object could be
+ // modified. By passing a mutable reference, the contents
+ // could change, but not the location.
+ println!("{:?}", foo)
+}
+
+pub fn test2() {
+ let foo: &Box<bool>;
+}
+
+struct Test3<'a> {
+ foo: &'a Box<bool>,
+}
+
+trait Test4 {
+ fn test4(a: &Box<bool>);
+}
+
+impl<'a> Test4 for Test3<'a> {
+ fn test4(a: &Box<bool>) {
+ unimplemented!();
+ }
+}
+
+use std::any::Any;
+
+pub fn test5(foo: &mut Box<dyn Any>) {
+ println!("{:?}", foo)
+}
+
+pub fn test6() {
+ let foo: &Box<dyn Any>;
+}
+
+struct Test7<'a> {
+ foo: &'a Box<dyn Any>,
+}
+
+trait Test8 {
+ fn test8(a: &Box<dyn Any>);
+}
+
+impl<'a> Test8 for Test7<'a> {
+ fn test8(a: &Box<dyn Any>) {
+ unimplemented!();
+ }
+}
+
+pub fn test9(foo: &mut Box<dyn Any + Send + Sync>) {
+ let _ = foo;
+}
+
+pub fn test10() {
+ let foo: &Box<dyn Any + Send + 'static>;
+}
+
+struct Test11<'a> {
+ foo: &'a Box<dyn Any + Send>,
+}
+
+trait Test12 {
+ fn test4(a: &Box<dyn Any + 'static>);
+}
+
+impl<'a> Test12 for Test11<'a> {
+ fn test4(a: &Box<dyn Any + 'static>) {
+ unimplemented!();
+ }
+}
+
+pub fn test13(boxed_slice: &mut Box<[i32]>) {
+ // Unconditionally replaces the box pointer.
+ //
+ // This cannot be accomplished if "&mut [i32]" is passed,
+ // and provides a test case where passing a reference to
+ // a Box is valid.
+ let mut data = vec![12];
+ *boxed_slice = data.into_boxed_slice();
+}
+
+// The suggestion should include proper parentheses to avoid a syntax error.
+pub fn test14(_display: &Box<dyn Display>) {}
+pub fn test15(_display: &Box<dyn Display + Send>) {}
+pub fn test16<'a>(_display: &'a Box<dyn Display + 'a>) {}
+
+pub fn test17(_display: &Box<impl Display>) {}
+pub fn test18(_display: &Box<impl Display + Send>) {}
+pub fn test19<'a>(_display: &'a Box<impl Display + 'a>) {}
+
+// This exists only to check what happens when parentheses are already present.
+// Even though the current implementation doesn't put extra parentheses,
+// it's fine that unnecessary parentheses appear in the future for some reason.
+pub fn test20(_display: &Box<(dyn Display + Send)>) {}
+
+fn main() {
+ test1(&mut Box::new(false));
+ test2();
+ test5(&mut (Box::new(false) as Box<dyn Any>));
+ test6();
+ test9(&mut (Box::new(false) as Box<dyn Any + Send + Sync>));
+ test10();
+}
--- /dev/null
- --> $DIR/borrow_box.rs:21:14
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:25:10
++ --> $DIR/borrow_box.rs:20:14
+ |
+LL | let foo: &Box<bool>;
+ | ^^^^^^^^^^ help: try: `&bool`
+ |
+note: the lint level is defined here
+ --> $DIR/borrow_box.rs:1:9
+ |
+LL | #![deny(clippy::borrowed_box)]
+ | ^^^^^^^^^^^^^^^^^^^^
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:29:17
++ --> $DIR/borrow_box.rs:24:10
+ |
+LL | foo: &'a Box<bool>,
+ | ^^^^^^^^^^^^^ help: try: `&'a bool`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:95:25
++ --> $DIR/borrow_box.rs:28:17
+ |
+LL | fn test4(a: &Box<bool>);
+ | ^^^^^^^^^^ help: try: `&bool`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:96:25
++ --> $DIR/borrow_box.rs:94:25
+ |
+LL | pub fn test14(_display: &Box<dyn Display>) {}
+ | ^^^^^^^^^^^^^^^^^ help: try: `&dyn Display`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:97:29
++ --> $DIR/borrow_box.rs:95:25
+ |
+LL | pub fn test15(_display: &Box<dyn Display + Send>) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&(dyn Display + Send)`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:99:25
++ --> $DIR/borrow_box.rs:96:29
+ |
+LL | pub fn test16<'a>(_display: &'a Box<dyn Display + 'a>) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&'a (dyn Display + 'a)`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:100:25
++ --> $DIR/borrow_box.rs:98:25
+ |
+LL | pub fn test17(_display: &Box<impl Display>) {}
+ | ^^^^^^^^^^^^^^^^^^ help: try: `&impl Display`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:101:29
++ --> $DIR/borrow_box.rs:99:25
+ |
+LL | pub fn test18(_display: &Box<impl Display + Send>) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&(impl Display + Send)`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
- --> $DIR/borrow_box.rs:106:25
++ --> $DIR/borrow_box.rs:100:29
+ |
+LL | pub fn test19<'a>(_display: &'a Box<impl Display + 'a>) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&'a (impl Display + 'a)`
+
+error: you seem to be trying to use `&Box<T>`. Consider using just `&T`
++ --> $DIR/borrow_box.rs:105:25
+ |
+LL | pub fn test20(_display: &Box<(dyn Display + Send)>) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&(dyn Display + Send)`
+
+error: aborting due to 10 previous errors
+
--- /dev/null
- boxit!(Vec::new(), Vec<u8>);
+#![warn(clippy::all)]
+#![allow(
+ clippy::boxed_local,
+ clippy::needless_pass_by_value,
+ clippy::disallowed_names,
+ unused
+)]
+
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
+
+macro_rules! boxit {
+ ($init:expr, $x:ty) => {
+ let _: Box<$x> = Box::new($init);
+ };
+}
+
+fn test_macro() {
- Box::new(Vec::new())
++ boxit!(vec![1], Vec<u8>);
+}
+
+fn test1(foo: Box<Vec<bool>>) {}
+
+fn test2(foo: Box<dyn Fn(Vec<u32>)>) {
+ // pass if #31 is fixed
+ foo(vec![1, 2, 3])
+}
+
+fn test3(foo: Box<String>) {}
+
+fn test4(foo: Box<HashMap<String, String>>) {}
+
+fn test5(foo: Box<HashSet<i64>>) {}
+
+fn test6(foo: Box<VecDeque<i32>>) {}
+
+fn test7(foo: Box<LinkedList<i16>>) {}
+
+fn test8(foo: Box<BTreeMap<i8, String>>) {}
+
+fn test9(foo: Box<BTreeSet<u64>>) {}
+
+fn test10(foo: Box<BinaryHeap<u32>>) {}
+
+fn test_local_not_linted() {
+ let _: Box<Vec<bool>>;
+}
+
+// All of these test should be allowed because they are part of the
+// public api and `avoid_breaking_exported_api` is `false` by default.
+pub fn pub_test(foo: Box<Vec<bool>>) {}
+
+pub fn pub_test_ret() -> Box<Vec<bool>> {
++ Box::default()
+}
+
+fn main() {}
--- /dev/null
--- /dev/null
++#![warn(clippy::box_default)]
++
++#[derive(Default)]
++struct ImplementsDefault;
++
++struct OwnDefault;
++
++impl OwnDefault {
++ fn default() -> Self {
++ Self
++ }
++}
++
++macro_rules! outer {
++ ($e: expr) => {
++ $e
++ };
++}
++
++fn main() {
++ let _string: Box<String> = Box::new(Default::default());
++ let _byte = Box::new(u8::default());
++ let _vec = Box::new(Vec::<u8>::new());
++ let _impl = Box::new(ImplementsDefault::default());
++ let _impl2 = Box::new(<ImplementsDefault as Default>::default());
++ let _impl3: Box<ImplementsDefault> = Box::new(Default::default());
++ let _own = Box::new(OwnDefault::default()); // should not lint
++ let _in_macro = outer!(Box::new(String::new()));
++ // false negative: default is from different expansion
++ let _vec2: Box<Vec<ImplementsDefault>> = Box::new(vec![]);
++}
--- /dev/null
--- /dev/null
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:21:32
++ |
++LL | let _string: Box<String> = Box::new(Default::default());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++ = note: `-D clippy::box-default` implied by `-D warnings`
++
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:22:17
++ |
++LL | let _byte = Box::new(u8::default());
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:23:16
++ |
++LL | let _vec = Box::new(Vec::<u8>::new());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:24:17
++ |
++LL | let _impl = Box::new(ImplementsDefault::default());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:25:18
++ |
++LL | let _impl2 = Box::new(<ImplementsDefault as Default>::default());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:26:42
++ |
++LL | let _impl3: Box<ImplementsDefault> = Box::new(Default::default());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++
++error: `Box::new(_)` of default value
++ --> $DIR/box_default.rs:28:28
++ |
++LL | let _in_macro = outer!(Box::new(String::new()));
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: use `Box::default()` instead
++
++error: aborting due to 7 previous errors
++
--- /dev/null
- #![allow(dead_code, clippy::equatable_if_let)]
+#![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
++#![allow(dead_code)]
++#![allow(clippy::equatable_if_let, clippy::uninlined_format_args)]
+
+// This tests the branches_sharing_code lint at the end of blocks
+
+fn simple_examples() {
+ let x = 1;
+
+ let _ = if x == 7 {
+ println!("Branch I");
+ let start_value = 0;
+ println!("=^.^=");
+
+ // Same but not moveable due to `start_value`
+ let _ = start_value;
+
+ // The rest is self contained and moveable => Only lint the rest
+ let result = false;
+ println!("Block end!");
+ result
+ } else {
+ println!("Branch II");
+ let start_value = 8;
+ println!("xD");
+
+ // Same but not moveable due to `start_value`
+ let _ = start_value;
+
+ // The rest is self contained and moveable => Only lint the rest
+ let result = false;
+ println!("Block end!");
+ result
+ };
+
+ // Else if block
+ if x == 9 {
+ println!("The index is: 6");
+
+ println!("Same end of block");
+ } else if x == 8 {
+ println!("The index is: 4");
+
+ // We should only get a lint trigger for the last statement
+ println!("This is also eq with the else block");
+ println!("Same end of block");
+ } else {
+ println!("This is also eq with the else block");
+ println!("Same end of block");
+ }
+
+ // Use of outer scope value
+ let outer_scope_value = "I'm outside the if block";
+ if x < 99 {
+ let z = "How are you";
+ println!("I'm a local because I use the value `z`: `{}`", z);
+
+ println!(
+ "I'm moveable because I know: `outer_scope_value`: '{}'",
+ outer_scope_value
+ );
+ } else {
+ let z = 45678000;
+ println!("I'm a local because I use the value `z`: `{}`", z);
+
+ println!(
+ "I'm moveable because I know: `outer_scope_value`: '{}'",
+ outer_scope_value
+ );
+ }
+
+ if x == 9 {
+ if x == 8 {
+ // No parent!!
+ println!("---");
+ println!("Hello World");
+ } else {
+ println!("Hello World");
+ }
+ }
+}
+
+/// Simple examples where the move can cause some problems due to moved values
+fn simple_but_suggestion_is_invalid() {
+ let x = 16;
+
+ // Local value
+ let later_used_value = 17;
+ if x == 9 {
+ let _ = 9;
+ let later_used_value = "A string value";
+ println!("{}", later_used_value);
+ } else {
+ let later_used_value = "A string value";
+ println!("{}", later_used_value);
+ // I'm expecting a note about this
+ }
+ println!("{}", later_used_value);
+
+ // outer function
+ if x == 78 {
+ let simple_examples = "I now identify as a &str :)";
+ println!("This is the new simple_example: {}", simple_examples);
+ } else {
+ println!("Separator print statement");
+
+ let simple_examples = "I now identify as a &str :)";
+ println!("This is the new simple_example: {}", simple_examples);
+ }
+ simple_examples();
+}
+
+/// Tests where the blocks are not linted due to the used value scope
+fn not_moveable_due_to_value_scope() {
+ let x = 18;
+
+ // Using a local value in the moved code
+ if x == 9 {
+ let y = 18;
+ println!("y is: `{}`", y);
+ } else {
+ let y = "A string";
+ println!("y is: `{}`", y);
+ }
+
+ // Using a local value in the expression
+ let _ = if x == 0 {
+ let mut result = x + 1;
+
+ println!("1. Doing some calculations");
+ println!("2. Some more calculations");
+ println!("3. Setting result");
+
+ result
+ } else {
+ let mut result = x - 1;
+
+ println!("1. Doing some calculations");
+ println!("2. Some more calculations");
+ println!("3. Setting result");
+
+ result
+ };
+
+ let _ = if x == 7 {
+ let z1 = 100;
+ println!("z1: {}", z1);
+
+ let z2 = z1;
+ println!("z2: {}", z2);
+
+ z2
+ } else {
+ let z1 = 300;
+ println!("z1: {}", z1);
+
+ let z2 = z1;
+ println!("z2: {}", z2);
+
+ z2
+ };
+}
+
+/// This should add a note to the lint msg since the moved expression is not `()`
+fn added_note_for_expression_use() -> u32 {
+ let x = 9;
+
+ let _ = if x == 7 {
+ x << 2
+ } else {
+ let _ = 6;
+ x << 2
+ };
+
+ if x == 9 {
+ x * 4
+ } else {
+ let _ = 17;
+ x * 4
+ }
+}
+
+#[rustfmt::skip]
+fn test_suggestion_with_weird_formatting() {
+ let x = 9;
+ let mut a = 0;
+ let mut b = 0;
+
+ // The error message still looks weird tbh but this is the best I can do
+ // for weird formatting
+ if x == 17 { b = 1; a = 0x99; } else { a = 0x99; }
+}
+
+fn fp_test() {
+ let x = 17;
+
+ if x == 18 {
+ let y = 19;
+ if y < x {
+ println!("Trigger")
+ }
+ } else {
+ let z = 166;
+ if z < x {
+ println!("Trigger")
+ }
+ }
+}
+
+fn fp_if_let_issue7054() {
+ // This shouldn't trigger the lint
+ let string;
+ let _x = if let true = true {
+ ""
+ } else if true {
+ string = "x".to_owned();
+ &string
+ } else {
+ string = "y".to_owned();
+ &string
+ };
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/shared_at_bottom.rs:30:5
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:2:36
++ --> $DIR/shared_at_bottom.rs:31:5
+ |
+LL | / let result = false;
+LL | | println!("Block end!");
+LL | | result
+LL | | };
+ | |_____^
+ |
+ = note: the end suggestion probably needs some adjustments to use the expression result correctly
+note: the lint level is defined here
- --> $DIR/shared_at_bottom.rs:48:5
++ --> $DIR/shared_at_bottom.rs:1:36
+ |
+LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + let result = false;
+LL + println!("Block end!");
+LL ~ result;
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:65:5
++ --> $DIR/shared_at_bottom.rs:49:5
+ |
+LL | / println!("Same end of block");
+LL | | }
+ | |_____^
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + println!("Same end of block");
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:77:9
++ --> $DIR/shared_at_bottom.rs:66:5
+ |
+LL | / println!(
+LL | | "I'm moveable because I know: `outer_scope_value`: '{}'",
+LL | | outer_scope_value
+LL | | );
+LL | | }
+ | |_____^
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + println!(
+LL + "I'm moveable because I know: `outer_scope_value`: '{}'",
+LL + outer_scope_value
+LL + );
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:93:5
++ --> $DIR/shared_at_bottom.rs:78:9
+ |
+LL | / println!("Hello World");
+LL | | }
+ | |_________^
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + println!("Hello World");
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:106:5
++ --> $DIR/shared_at_bottom.rs:94:5
+ |
+LL | / let later_used_value = "A string value";
+LL | | println!("{}", later_used_value);
+LL | | // I'm expecting a note about this
+LL | | }
+ | |_____^
+ |
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + let later_used_value = "A string value";
+LL + println!("{}", later_used_value);
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:171:5
++ --> $DIR/shared_at_bottom.rs:107:5
+ |
+LL | / let simple_examples = "I now identify as a &str :)";
+LL | | println!("This is the new simple_example: {}", simple_examples);
+LL | | }
+ | |_____^
+ |
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + let simple_examples = "I now identify as a &str :)";
+LL + println!("This is the new simple_example: {}", simple_examples);
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:178:5
++ --> $DIR/shared_at_bottom.rs:172:5
+ |
+LL | / x << 2
+LL | | };
+ | |_____^
+ |
+ = note: the end suggestion probably needs some adjustments to use the expression result correctly
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL ~ x << 2;
+ |
+
+error: all if blocks contain the same code at the end
- --> $DIR/shared_at_bottom.rs:190:44
++ --> $DIR/shared_at_bottom.rs:179:5
+ |
+LL | / x * 4
+LL | | }
+ | |_____^
+ |
+ = note: the end suggestion probably needs some adjustments to use the expression result correctly
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + x * 4
+ |
+
+error: all if blocks contain the same code at the end
++ --> $DIR/shared_at_bottom.rs:191:44
+ |
+LL | if x == 17 { b = 1; a = 0x99; } else { a = 0x99; }
+ | ^^^^^^^^^^^
+ |
+help: consider moving these statements after the if
+ |
+LL ~ if x == 17 { b = 1; a = 0x99; } else { }
+LL + a = 0x99;
+ |
+
+error: aborting due to 9 previous errors
+
--- /dev/null
- #![allow(dead_code, clippy::mixed_read_write_in_expression)]
- #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
++#![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
++#![allow(dead_code)]
++#![allow(clippy::mixed_read_write_in_expression, clippy::uninlined_format_args)]
+
+// This tests the branches_sharing_code lint at the start of blocks
+
+fn simple_examples() {
+ let x = 0;
+
+ // Simple
+ if true {
+ println!("Hello World!");
+ println!("I'm branch nr: 1");
+ } else {
+ println!("Hello World!");
+ println!("I'm branch nr: 2");
+ }
+
+ // Else if
+ if x == 0 {
+ let y = 9;
+ println!("The value y was set to: `{}`", y);
+ let _z = y;
+
+ println!("I'm the true start index of arrays");
+ } else if x == 1 {
+ let y = 9;
+ println!("The value y was set to: `{}`", y);
+ let _z = y;
+
+ println!("I start counting from 1 so my array starts from `1`");
+ } else {
+ let y = 9;
+ println!("The value y was set to: `{}`", y);
+ let _z = y;
+
+ println!("Ha, Pascal allows you to start the array where you want")
+ }
+
+ // Return a value
+ let _ = if x == 7 {
+ let y = 16;
+ println!("What can I say except: \"you're welcome?\"");
+ let _ = y;
+ x
+ } else {
+ let y = 16;
+ println!("Thank you");
+ y
+ };
+}
+
+/// Simple examples where the move can cause some problems due to moved values
+fn simple_but_suggestion_is_invalid() {
+ let x = 10;
+
+ // Can't be automatically moved because used_value_name is getting used again
+ let used_value_name = 19;
+ if x == 10 {
+ let used_value_name = "Different type";
+ println!("Str: {}", used_value_name);
+ let _ = 1;
+ } else {
+ let used_value_name = "Different type";
+ println!("Str: {}", used_value_name);
+ let _ = 2;
+ }
+ let _ = used_value_name;
+
+ // This can be automatically moved as `can_be_overridden` is not used again
+ let can_be_overridden = 8;
+ let _ = can_be_overridden;
+ if x == 11 {
+ let can_be_overridden = "Move me";
+ println!("I'm also moveable");
+ let _ = 111;
+ } else {
+ let can_be_overridden = "Move me";
+ println!("I'm also moveable");
+ let _ = 222;
+ }
+}
+
+/// This function tests that the `IS_SAME_THAN_ELSE` only covers the lint if it's enabled.
+fn check_if_same_than_else_mask() {
+ let x = 2021;
+
+ #[allow(clippy::if_same_then_else)]
+ if x == 2020 {
+ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint.");
+ println!("Because `IF_SAME_THEN_ELSE` is allowed here");
+ } else {
+ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint.");
+ println!("Because `IF_SAME_THEN_ELSE` is allowed here");
+ }
+
+ if x == 2019 {
+ println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
+ } else {
+ println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
+ }
+}
+
+#[allow(clippy::vec_init_then_push)]
+fn pf_local_with_inferred_type_issue7053() {
+ if true {
+ let mut v = Vec::new();
+ v.push(0);
+ } else {
+ let mut v = Vec::new();
+ v.push("");
+ };
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/shared_at_top.rs:10:5
+error: all if blocks contain the same code at the start
- --> $DIR/shared_at_top.rs:2:36
++ --> $DIR/shared_at_top.rs:11:5
+ |
+LL | / if true {
+LL | | println!("Hello World!");
+ | |_________________________________^
+ |
+note: the lint level is defined here
- LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/shared_at_top.rs:1:9
+ |
- --> $DIR/shared_at_top.rs:19:5
++LL | #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: consider moving these statements before the if
+ |
+LL ~ println!("Hello World!");
+LL + if true {
+ |
+
+error: all if blocks contain the same code at the start
- --> $DIR/shared_at_top.rs:40:5
++ --> $DIR/shared_at_top.rs:20:5
+ |
+LL | / if x == 0 {
+LL | | let y = 9;
+LL | | println!("The value y was set to: `{}`", y);
+LL | | let _z = y;
+ | |___________________^
+ |
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements before the if
+ |
+LL ~ let y = 9;
+LL + println!("The value y was set to: `{}`", y);
+LL + let _z = y;
+LL + if x == 0 {
+ |
+
+error: all if blocks contain the same code at the start
- --> $DIR/shared_at_top.rs:58:5
++ --> $DIR/shared_at_top.rs:41:5
+ |
+LL | / let _ = if x == 7 {
+LL | | let y = 16;
+ | |___________________^
+ |
+help: consider moving these statements before the if
+ |
+LL ~ let y = 16;
+LL + let _ = if x == 7 {
+ |
+
+error: all if blocks contain the same code at the start
- --> $DIR/shared_at_top.rs:72:5
++ --> $DIR/shared_at_top.rs:59:5
+ |
+LL | / if x == 10 {
+LL | | let used_value_name = "Different type";
+LL | | println!("Str: {}", used_value_name);
+ | |_____________________________________________^
+ |
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements before the if
+ |
+LL ~ let used_value_name = "Different type";
+LL + println!("Str: {}", used_value_name);
+LL + if x == 10 {
+ |
+
+error: all if blocks contain the same code at the start
- --> $DIR/shared_at_top.rs:88:5
++ --> $DIR/shared_at_top.rs:73:5
+ |
+LL | / if x == 11 {
+LL | | let can_be_overridden = "Move me";
+LL | | println!("I'm also moveable");
+ | |______________________________________^
+ |
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements before the if
+ |
+LL ~ let can_be_overridden = "Move me";
+LL + println!("I'm also moveable");
+LL + if x == 11 {
+ |
+
+error: all if blocks contain the same code at the start
- --> $DIR/shared_at_top.rs:96:18
++ --> $DIR/shared_at_top.rs:89:5
+ |
+LL | / if x == 2020 {
+LL | | println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint.");
+LL | | println!("Because `IF_SAME_THEN_ELSE` is allowed here");
+ | |________________________________________________________________^
+ |
+help: consider moving these statements before the if
+ |
+LL ~ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint.");
+LL + println!("Because `IF_SAME_THEN_ELSE` is allowed here");
+LL + if x == 2020 {
+ |
+
+error: this `if` has identical blocks
- --> $DIR/shared_at_top.rs:98:12
++ --> $DIR/shared_at_top.rs:97:18
+ |
+LL | if x == 2019 {
+ | __________________^
+LL | | println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
+LL | | } else {
+ | |_____^
+ |
+note: same as this
- --> $DIR/shared_at_top.rs:2:9
++ --> $DIR/shared_at_top.rs:99:12
+ |
+LL | } else {
+ | ____________^
+LL | | println!("This should trigger `IS_SAME_THAN_ELSE` as usual");
+LL | | }
+ | |_____^
+note: the lint level is defined here
- LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/shared_at_top.rs:1:40
+ |
++LL | #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 7 previous errors
+
--- /dev/null
- #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
++#![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
+#![allow(dead_code)]
++#![allow(clippy::uninlined_format_args)]
+
+// branches_sharing_code at the top and bottom of the if blocks
+
+struct DataPack {
+ id: u32,
+ name: String,
+ some_data: Vec<u8>,
+}
+
+fn overlapping_eq_regions() {
+ let x = 9;
+
+ // Overlap with separator
+ if x == 7 {
+ let t = 7;
+ let _overlap_start = t * 2;
+ let _overlap_end = 2 * t;
+ let _u = 9;
+ } else {
+ let t = 7;
+ let _overlap_start = t * 2;
+ let _overlap_end = 2 * t;
+ println!("Overlap separator");
+ let _overlap_start = t * 2;
+ let _overlap_end = 2 * t;
+ let _u = 9;
+ }
+
+ // Overlap with separator
+ if x == 99 {
+ let r = 7;
+ let _overlap_start = r;
+ let _overlap_middle = r * r;
+ let _overlap_end = r * r * r;
+ let z = "end";
+ } else {
+ let r = 7;
+ let _overlap_start = r;
+ let _overlap_middle = r * r;
+ let _overlap_middle = r * r;
+ let _overlap_end = r * r * r;
+ let z = "end";
+ }
+}
+
+fn complexer_example() {
+ fn gen_id(x: u32, y: u32) -> u32 {
+ let x = x & 0x0000_ffff;
+ let y = (y & 0xffff_0000) << 16;
+ x | y
+ }
+
+ fn process_data(data: DataPack) {
+ let _ = data;
+ }
+
+ let x = 8;
+ let y = 9;
+ if (x > 7 && y < 13) || (x + y) % 2 == 1 {
+ let a = 0xcafe;
+ let b = 0xffff00ff;
+ let e_id = gen_id(a, b);
+
+ println!("From the a `{}` to the b `{}`", a, b);
+
+ let pack = DataPack {
+ id: e_id,
+ name: "Player 1".to_string(),
+ some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90],
+ };
+ process_data(pack);
+ } else {
+ let a = 0xcafe;
+ let b = 0xffff00ff;
+ let e_id = gen_id(a, b);
+
+ println!("The new ID is '{}'", e_id);
+
+ let pack = DataPack {
+ id: e_id,
+ name: "Player 1".to_string(),
+ some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90],
+ };
+ process_data(pack);
+ }
+}
+
+/// This should add a note to the lint msg since the moved expression is not `()`
+fn added_note_for_expression_use() -> u32 {
+ let x = 9;
+
+ let _ = if x == 7 {
+ let _ = 19;
+
+ let _splitter = 6;
+
+ x << 2
+ } else {
+ let _ = 19;
+
+ x << 2
+ };
+
+ if x == 9 {
+ let _ = 17;
+
+ let _splitter = 6;
+
+ x * 4
+ } else {
+ let _ = 17;
+
+ x * 4
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/shared_at_top_and_bottom.rs:16:5
+error: all if blocks contain the same code at both the start and the end
- --> $DIR/shared_at_top_and_bottom.rs:28:5
++ --> $DIR/shared_at_top_and_bottom.rs:17:5
+ |
+LL | / if x == 7 {
+LL | | let t = 7;
+LL | | let _overlap_start = t * 2;
+LL | | let _overlap_end = 2 * t;
+ | |_________________________________^
+ |
+note: this code is shared at the end
- --> $DIR/shared_at_top_and_bottom.rs:2:36
++ --> $DIR/shared_at_top_and_bottom.rs:29:5
+ |
+LL | / let _u = 9;
+LL | | }
+ | |_____^
+note: the lint level is defined here
- LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/shared_at_top_and_bottom.rs:1:9
+ |
- --> $DIR/shared_at_top_and_bottom.rs:32:5
++LL | #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: consider moving these statements before the if
+ |
+LL ~ let t = 7;
+LL + let _overlap_start = t * 2;
+LL + let _overlap_end = 2 * t;
+LL + if x == 7 {
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + let _u = 9;
+ |
+
+error: all if blocks contain the same code at both the start and the end
- --> $DIR/shared_at_top_and_bottom.rs:43:5
++ --> $DIR/shared_at_top_and_bottom.rs:33:5
+ |
+LL | / if x == 99 {
+LL | | let r = 7;
+LL | | let _overlap_start = r;
+LL | | let _overlap_middle = r * r;
+ | |____________________________________^
+ |
+note: this code is shared at the end
- --> $DIR/shared_at_top_and_bottom.rs:61:5
++ --> $DIR/shared_at_top_and_bottom.rs:44:5
+ |
+LL | / let _overlap_end = r * r * r;
+LL | | let z = "end";
+LL | | }
+ | |_____^
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements before the if
+ |
+LL ~ let r = 7;
+LL + let _overlap_start = r;
+LL + let _overlap_middle = r * r;
+LL + if x == 99 {
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + let _overlap_end = r * r * r;
+LL + let z = "end";
+ |
+
+error: all if blocks contain the same code at both the start and the end
- --> $DIR/shared_at_top_and_bottom.rs:81:5
++ --> $DIR/shared_at_top_and_bottom.rs:62:5
+ |
+LL | / if (x > 7 && y < 13) || (x + y) % 2 == 1 {
+LL | | let a = 0xcafe;
+LL | | let b = 0xffff00ff;
+LL | | let e_id = gen_id(a, b);
+ | |________________________________^
+ |
+note: this code is shared at the end
- --> $DIR/shared_at_top_and_bottom.rs:94:5
++ --> $DIR/shared_at_top_and_bottom.rs:82:5
+ |
+LL | / let pack = DataPack {
+LL | | id: e_id,
+LL | | name: "Player 1".to_string(),
+LL | | some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90],
+LL | | };
+LL | | process_data(pack);
+LL | | }
+ | |_____^
+ = warning: some moved values might need to be renamed to avoid wrong references
+help: consider moving these statements before the if
+ |
+LL ~ let a = 0xcafe;
+LL + let b = 0xffff00ff;
+LL + let e_id = gen_id(a, b);
+LL + if (x > 7 && y < 13) || (x + y) % 2 == 1 {
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + let pack = DataPack {
+LL + id: e_id,
+LL + name: "Player 1".to_string(),
+LL + some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90],
+LL + };
+LL + process_data(pack);
+ |
+
+error: all if blocks contain the same code at both the start and the end
- --> $DIR/shared_at_top_and_bottom.rs:103:5
++ --> $DIR/shared_at_top_and_bottom.rs:95:5
+ |
+LL | / let _ = if x == 7 {
+LL | | let _ = 19;
+ | |___________________^
+ |
+note: this code is shared at the end
- --> $DIR/shared_at_top_and_bottom.rs:106:5
++ --> $DIR/shared_at_top_and_bottom.rs:104:5
+ |
+LL | / x << 2
+LL | | };
+ | |_____^
+ = note: the end suggestion probably needs some adjustments to use the expression result correctly
+help: consider moving these statements before the if
+ |
+LL ~ let _ = 19;
+LL + let _ = if x == 7 {
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL ~ x << 2;
+ |
+
+error: all if blocks contain the same code at both the start and the end
- --> $DIR/shared_at_top_and_bottom.rs:115:5
++ --> $DIR/shared_at_top_and_bottom.rs:107:5
+ |
+LL | / if x == 9 {
+LL | | let _ = 17;
+ | |___________________^
+ |
+note: this code is shared at the end
++ --> $DIR/shared_at_top_and_bottom.rs:116:5
+ |
+LL | / x * 4
+LL | | }
+ | |_____^
+ = note: the end suggestion probably needs some adjustments to use the expression result correctly
+help: consider moving these statements before the if
+ |
+LL ~ let _ = 17;
+LL + if x == 9 {
+ |
+help: consider moving these statements after the if
+ |
+LL ~ }
+LL + x * 4
+ |
+
+error: aborting due to 5 previous errors
+
--- /dev/null
- #![allow(dead_code, clippy::mixed_read_write_in_expression)]
- #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
++#![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
++#![allow(dead_code)]
++#![allow(clippy::mixed_read_write_in_expression, clippy::uninlined_format_args)]
+
+// This tests valid if blocks that shouldn't trigger the lint
+
+// Tests with value references are includes in "shared_code_at_bottom.rs"
+
+fn valid_examples() {
+ let x = 2;
+
+ // The edge statements are different
+ if x == 9 {
+ let y = 1 << 5;
+
+ println!("This is the same: vvv");
+ let _z = y;
+ println!("The block expression is different");
+
+ println!("Different end 1");
+ } else {
+ let y = 1 << 7;
+
+ println!("This is the same: vvv");
+ let _z = y;
+ println!("The block expression is different");
+
+ println!("Different end 2");
+ }
+
+ // No else
+ if x == 2 {
+ println!("Hello world!");
+ println!("Hello back, how are you?");
+
+ // This is different vvvv
+ println!("Howdy stranger =^.^=");
+
+ println!("Bye Bye World");
+ } else if x == 9 {
+ println!("Hello world!");
+ println!("Hello back, how are you?");
+
+ // This is different vvvv
+ println!("Hello reviewer :D");
+
+ println!("Bye Bye World");
+ }
+
+ // Overlapping statements only in else if blocks -> Don't lint
+ if x == 0 {
+ println!("I'm important!")
+ } else if x == 17 {
+ println!("I share code in else if");
+
+ println!("x is 17");
+ } else {
+ println!("I share code in else if");
+
+ println!("x is nether x nor 17");
+ }
+
+ // Mutability is different
+ if x == 13 {
+ let mut y = 9;
+ println!("Value y is: {}", y);
+ y += 16;
+ let _z1 = y;
+ } else {
+ let y = 9;
+ println!("Value y is: {}", y);
+ let _z2 = y;
+ }
+
+ // Same blocks but at start and bottom so no `if_same_then_else` lint
+ if x == 418 {
+ let y = 9;
+ let z = 8;
+ let _ = (x, y, z);
+ // Don't tell the programmer, my code is also in the else block
+ } else if x == 419 {
+ println!("+-----------+");
+ println!("| |");
+ println!("| O O |");
+ println!("| ° |");
+ println!("| \\_____/ |");
+ println!("| |");
+ println!("+-----------+");
+ } else {
+ let y = 9;
+ let z = 8;
+ let _ = (x, y, z);
+ // I'm so much better than the x == 418 block. Trust me
+ }
+
+ let x = 1;
+ if true {
+ println!("{}", x);
+ } else {
+ let x = 2;
+ println!("{}", x);
+ }
+
+ // Let's test empty blocks
+ if false {
+ } else {
+ }
+}
+
+/// This makes sure that the `if_same_then_else` masks the `shared_code_in_if_blocks` lint
+fn trigger_other_lint() {
+ let x = 0;
+ let y = 1;
+
+ // Same block
+ if x == 0 {
+ let u = 19;
+ println!("How are u today?");
+ let _ = "This is a string";
+ } else {
+ let u = 19;
+ println!("How are u today?");
+ let _ = "This is a string";
+ }
+
+ // Only same expression
+ let _ = if x == 6 { 7 } else { 7 };
+
+ // Same in else if block
+ let _ = if x == 67 {
+ println!("Well I'm the most important block");
+ "I'm a pretty string"
+ } else if x == 68 {
+ println!("I'm a doppelgänger");
+ // Don't listen to my clone below
+
+ if y == 90 { "=^.^=" } else { ":D" }
+ } else {
+ // Don't listen to my clone above
+ println!("I'm a doppelgänger");
+
+ if y == 90 { "=^.^=" } else { ":D" }
+ };
+
+ if x == 0 {
+ println!("I'm single");
+ } else if x == 68 {
+ println!("I'm a doppelgänger");
+ // Don't listen to my clone below
+ } else {
+ // Don't listen to my clone above
+ println!("I'm a doppelgänger");
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/valid_if_blocks.rs:104:14
+error: this `if` has identical blocks
- --> $DIR/valid_if_blocks.rs:105:12
++ --> $DIR/valid_if_blocks.rs:105:14
+ |
+LL | if false {
+ | ______________^
+LL | | } else {
+ | |_____^
+ |
+note: same as this
- --> $DIR/valid_if_blocks.rs:2:9
++ --> $DIR/valid_if_blocks.rs:106:12
+ |
+LL | } else {
+ | ____________^
+LL | | }
+ | |_____^
+note: the lint level is defined here
- LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)]
- | ^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/valid_if_blocks.rs:1:40
+ |
- --> $DIR/valid_if_blocks.rs:115:15
++LL | #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `if` has identical blocks
- --> $DIR/valid_if_blocks.rs:119:12
++ --> $DIR/valid_if_blocks.rs:116:15
+ |
+LL | if x == 0 {
+ | _______________^
+LL | | let u = 19;
+LL | | println!("How are u today?");
+LL | | let _ = "This is a string";
+LL | | } else {
+ | |_____^
+ |
+note: same as this
- --> $DIR/valid_if_blocks.rs:126:23
++ --> $DIR/valid_if_blocks.rs:120:12
+ |
+LL | } else {
+ | ____________^
+LL | | let u = 19;
+LL | | println!("How are u today?");
+LL | | let _ = "This is a string";
+LL | | }
+ | |_____^
+
+error: this `if` has identical blocks
- --> $DIR/valid_if_blocks.rs:126:34
++ --> $DIR/valid_if_blocks.rs:127:23
+ |
+LL | let _ = if x == 6 { 7 } else { 7 };
+ | ^^^^^
+ |
+note: same as this
- --> $DIR/valid_if_blocks.rs:132:23
++ --> $DIR/valid_if_blocks.rs:127:34
+ |
+LL | let _ = if x == 6 { 7 } else { 7 };
+ | ^^^^^
+
+error: this `if` has identical blocks
- --> $DIR/valid_if_blocks.rs:137:12
++ --> $DIR/valid_if_blocks.rs:133:23
+ |
+LL | } else if x == 68 {
+ | _______________________^
+LL | | println!("I'm a doppelgänger");
+LL | | // Don't listen to my clone below
+LL | |
+LL | | if y == 90 { "=^.^=" } else { ":D" }
+LL | | } else {
+ | |_____^
+ |
+note: same as this
- --> $DIR/valid_if_blocks.rs:146:23
++ --> $DIR/valid_if_blocks.rs:138:12
+ |
+LL | } else {
+ | ____________^
+LL | | // Don't listen to my clone above
+LL | | println!("I'm a doppelgänger");
+LL | |
+LL | | if y == 90 { "=^.^=" } else { ":D" }
+LL | | };
+ | |_____^
+
+error: this `if` has identical blocks
- --> $DIR/valid_if_blocks.rs:149:12
++ --> $DIR/valid_if_blocks.rs:147:23
+ |
+LL | } else if x == 68 {
+ | _______________________^
+LL | | println!("I'm a doppelgänger");
+LL | | // Don't listen to my clone below
+LL | | } else {
+ | |_____^
+ |
+note: same as this
++ --> $DIR/valid_if_blocks.rs:150:12
+ |
+LL | } else {
+ | ____________^
+LL | | // Don't listen to my clone above
+LL | | println!("I'm a doppelgänger");
+LL | | }
+ | |_____^
+
+error: aborting due to 5 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::cast_abs_to_unsigned)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ let x: i32 = -42;
+ let y: u32 = x.unsigned_abs();
+ println!("The absolute value of {} is {}", x, y);
+
+ let a: i32 = -3;
+ let _: usize = a.unsigned_abs() as usize;
+ let _: usize = a.unsigned_abs() as _;
+ let _ = a.unsigned_abs() as usize;
+
+ let a: i64 = -3;
+ let _ = a.unsigned_abs() as usize;
+ let _ = a.unsigned_abs() as u8;
+ let _ = a.unsigned_abs() as u16;
+ let _ = a.unsigned_abs() as u32;
+ let _ = a.unsigned_abs();
+ let _ = a.unsigned_abs() as u128;
+
+ let a: isize = -3;
+ let _ = a.unsigned_abs();
+ let _ = a.unsigned_abs() as u8;
+ let _ = a.unsigned_abs() as u16;
+ let _ = a.unsigned_abs() as u32;
+ let _ = a.unsigned_abs() as u64;
+ let _ = a.unsigned_abs() as u128;
+
+ let _ = (x as i64 - y as i64).unsigned_abs() as u32;
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::cast_abs_to_unsigned)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ let x: i32 = -42;
+ let y: u32 = x.abs() as u32;
+ println!("The absolute value of {} is {}", x, y);
+
+ let a: i32 = -3;
+ let _: usize = a.abs() as usize;
+ let _: usize = a.abs() as _;
+ let _ = a.abs() as usize;
+
+ let a: i64 = -3;
+ let _ = a.abs() as usize;
+ let _ = a.abs() as u8;
+ let _ = a.abs() as u16;
+ let _ = a.abs() as u32;
+ let _ = a.abs() as u64;
+ let _ = a.abs() as u128;
+
+ let a: isize = -3;
+ let _ = a.abs() as usize;
+ let _ = a.abs() as u8;
+ let _ = a.abs() as u16;
+ let _ = a.abs() as u32;
+ let _ = a.abs() as u64;
+ let _ = a.abs() as u128;
+
+ let _ = (x as i64 - y as i64).abs() as u32;
+}
--- /dev/null
- --> $DIR/cast_abs_to_unsigned.rs:6:18
+error: casting the result of `i32::abs()` to u32
- --> $DIR/cast_abs_to_unsigned.rs:10:20
++ --> $DIR/cast_abs_to_unsigned.rs:7:18
+ |
+LL | let y: u32 = x.abs() as u32;
+ | ^^^^^^^^^^^^^^ help: replace with: `x.unsigned_abs()`
+ |
+ = note: `-D clippy::cast-abs-to-unsigned` implied by `-D warnings`
+
+error: casting the result of `i32::abs()` to usize
- --> $DIR/cast_abs_to_unsigned.rs:11:20
++ --> $DIR/cast_abs_to_unsigned.rs:11:20
+ |
+LL | let _: usize = a.abs() as usize;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i32::abs()` to usize
- --> $DIR/cast_abs_to_unsigned.rs:12:13
++ --> $DIR/cast_abs_to_unsigned.rs:12:20
+ |
+LL | let _: usize = a.abs() as _;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i32::abs()` to usize
- --> $DIR/cast_abs_to_unsigned.rs:15:13
++ --> $DIR/cast_abs_to_unsigned.rs:13:13
+ |
+LL | let _ = a.abs() as usize;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to usize
- --> $DIR/cast_abs_to_unsigned.rs:16:13
++ --> $DIR/cast_abs_to_unsigned.rs:16:13
+ |
+LL | let _ = a.abs() as usize;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to u8
- --> $DIR/cast_abs_to_unsigned.rs:17:13
++ --> $DIR/cast_abs_to_unsigned.rs:17:13
+ |
+LL | let _ = a.abs() as u8;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to u16
- --> $DIR/cast_abs_to_unsigned.rs:18:13
++ --> $DIR/cast_abs_to_unsigned.rs:18:13
+ |
+LL | let _ = a.abs() as u16;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to u32
- --> $DIR/cast_abs_to_unsigned.rs:19:13
++ --> $DIR/cast_abs_to_unsigned.rs:19:13
+ |
+LL | let _ = a.abs() as u32;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to u64
- --> $DIR/cast_abs_to_unsigned.rs:20:13
++ --> $DIR/cast_abs_to_unsigned.rs:20:13
+ |
+LL | let _ = a.abs() as u64;
+ | ^^^^^^^^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to u128
- --> $DIR/cast_abs_to_unsigned.rs:23:13
++ --> $DIR/cast_abs_to_unsigned.rs:21:13
+ |
+LL | let _ = a.abs() as u128;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `isize::abs()` to usize
- --> $DIR/cast_abs_to_unsigned.rs:24:13
++ --> $DIR/cast_abs_to_unsigned.rs:24:13
+ |
+LL | let _ = a.abs() as usize;
+ | ^^^^^^^^^^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `isize::abs()` to u8
- --> $DIR/cast_abs_to_unsigned.rs:25:13
++ --> $DIR/cast_abs_to_unsigned.rs:25:13
+ |
+LL | let _ = a.abs() as u8;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `isize::abs()` to u16
- --> $DIR/cast_abs_to_unsigned.rs:26:13
++ --> $DIR/cast_abs_to_unsigned.rs:26:13
+ |
+LL | let _ = a.abs() as u16;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `isize::abs()` to u32
- --> $DIR/cast_abs_to_unsigned.rs:27:13
++ --> $DIR/cast_abs_to_unsigned.rs:27:13
+ |
+LL | let _ = a.abs() as u32;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `isize::abs()` to u64
- --> $DIR/cast_abs_to_unsigned.rs:28:13
++ --> $DIR/cast_abs_to_unsigned.rs:28:13
+ |
+LL | let _ = a.abs() as u64;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `isize::abs()` to u128
- --> $DIR/cast_abs_to_unsigned.rs:30:13
++ --> $DIR/cast_abs_to_unsigned.rs:29:13
+ |
+LL | let _ = a.abs() as u128;
+ | ^^^^^^^ help: replace with: `a.unsigned_abs()`
+
+error: casting the result of `i64::abs()` to u32
++ --> $DIR/cast_abs_to_unsigned.rs:31:13
+ |
+LL | let _ = (x as i64 - y as i64).abs() as u32;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `(x as i64 - y as i64).unsigned_abs()`
+
+error: aborting due to 17 previous errors
+
--- /dev/null
- clippy::equatable_if_let
+#![warn(clippy::collapsible_match)]
+#![allow(
++ clippy::equatable_if_let,
+ clippy::needless_return,
+ clippy::no_effect,
+ clippy::single_match,
++ clippy::uninlined_format_args
+)]
+
+fn lint_cases(opt_opt: Option<Option<u32>>, res_opt: Result<Option<u32>, String>) {
+ // match without block
+ match res_opt {
+ Ok(val) => match val {
+ Some(n) => foo(n),
+ _ => return,
+ },
+ _ => return,
+ }
+
+ // match with block
+ match res_opt {
+ Ok(val) => match val {
+ Some(n) => foo(n),
+ _ => return,
+ },
+ _ => return,
+ }
+
+ // if let, if let
+ if let Ok(val) = res_opt {
+ if let Some(n) = val {
+ take(n);
+ }
+ }
+
+ // if let else, if let else
+ if let Ok(val) = res_opt {
+ if let Some(n) = val {
+ take(n);
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ // if let, match
+ if let Ok(val) = res_opt {
+ match val {
+ Some(n) => foo(n),
+ _ => (),
+ }
+ }
+
+ // match, if let
+ match res_opt {
+ Ok(val) => {
+ if let Some(n) = val {
+ take(n);
+ }
+ },
+ _ => {},
+ }
+
+ // if let else, match
+ if let Ok(val) = res_opt {
+ match val {
+ Some(n) => foo(n),
+ _ => return,
+ }
+ } else {
+ return;
+ }
+
+ // match, if let else
+ match res_opt {
+ Ok(val) => {
+ if let Some(n) = val {
+ take(n);
+ } else {
+ return;
+ }
+ },
+ _ => return,
+ }
+
+ // None in inner match same as outer wild branch
+ match res_opt {
+ Ok(val) => match val {
+ Some(n) => foo(n),
+ None => return,
+ },
+ _ => return,
+ }
+
+ // None in outer match same as inner wild branch
+ match opt_opt {
+ Some(val) => match val {
+ Some(n) => foo(n),
+ _ => return,
+ },
+ None => return,
+ }
+}
+
+fn negative_cases(res_opt: Result<Option<u32>, String>, res_res: Result<Result<u32, String>, String>) {
+ while let Some(x) = make() {
+ if let Some(1) = x {
+ todo!();
+ }
+ }
+ // no wild pattern in outer match
+ match res_opt {
+ Ok(val) => match val {
+ Some(n) => foo(n),
+ _ => return,
+ },
+ Err(_) => return,
+ }
+
+ // inner branch is not wild or None
+ match res_res {
+ Ok(val) => match val {
+ Ok(n) => foo(n),
+ Err(_) => return,
+ },
+ _ => return,
+ }
+
+ // statement before inner match
+ match res_opt {
+ Ok(val) => {
+ "hi buddy";
+ match val {
+ Some(n) => foo(n),
+ _ => return,
+ }
+ },
+ _ => return,
+ }
+
+ // statement after inner match
+ match res_opt {
+ Ok(val) => {
+ match val {
+ Some(n) => foo(n),
+ _ => return,
+ }
+ "hi buddy";
+ },
+ _ => return,
+ }
+
+ // wild branches do not match
+ match res_opt {
+ Ok(val) => match val {
+ Some(n) => foo(n),
+ _ => {
+ "sup";
+ return;
+ },
+ },
+ _ => return,
+ }
+
+ // binding used in if guard
+ match res_opt {
+ Ok(val) if val.is_some() => match val {
+ Some(n) => foo(n),
+ _ => return,
+ },
+ _ => return,
+ }
+
+ // binding used in inner match body
+ match res_opt {
+ Ok(val) => match val {
+ Some(_) => take(val),
+ _ => return,
+ },
+ _ => return,
+ }
+
+ // if guard on inner match
+ {
+ match res_opt {
+ Ok(val) => match val {
+ Some(n) if make() => foo(n),
+ _ => return,
+ },
+ _ => return,
+ }
+ match res_opt {
+ Ok(val) => match val {
+ _ => make(),
+ _ if make() => return,
+ },
+ _ => return,
+ }
+ }
+
+ // differing macro contexts
+ {
+ macro_rules! mac {
+ ($val:ident) => {
+ match $val {
+ Some(n) => foo(n),
+ _ => return,
+ }
+ };
+ }
+ match res_opt {
+ Ok(val) => mac!(val),
+ _ => return,
+ }
+ }
+
+ // OR pattern
+ enum E<T> {
+ A(T),
+ B(T),
+ C(T),
+ };
+ match make::<E<Option<u32>>>() {
+ E::A(val) | E::B(val) => match val {
+ Some(n) => foo(n),
+ _ => return,
+ },
+ _ => return,
+ }
+ match make::<Option<E<u32>>>() {
+ Some(val) => match val {
+ E::A(val) | E::B(val) => foo(val),
+ _ => return,
+ },
+ _ => return,
+ }
+ if let Ok(val) = res_opt {
+ if let Some(n) = val {
+ let _ = || {
+ // usage in closure
+ println!("{:?}", val);
+ };
+ }
+ }
+ let _: &dyn std::any::Any = match &Some(Some(1)) {
+ Some(e) => match e {
+ Some(e) => e,
+ e => e,
+ },
+ // else branch looks the same but the binding is different
+ e => e,
+ };
+}
+
+fn make<T>() -> T {
+ unimplemented!()
+}
+
+fn foo<T, U>(t: T) -> U {
+ unimplemented!()
+}
+
+fn take<T>(t: T) {}
+
+fn main() {}
--- /dev/null
- --> $DIR/collapsible_match.rs:12:20
+error: this `match` can be collapsed into the outer `match`
- --> $DIR/collapsible_match.rs:12:12
++ --> $DIR/collapsible_match.rs:13:20
+ |
+LL | Ok(val) => match val {
+ | ____________________^
+LL | | Some(n) => foo(n),
+LL | | _ => return,
+LL | | },
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:21:20
++ --> $DIR/collapsible_match.rs:13:12
+ |
+LL | Ok(val) => match val {
+ | ^^^ replace this binding
+LL | Some(n) => foo(n),
+ | ^^^^^^^ with this pattern
+ = note: `-D clippy::collapsible-match` implied by `-D warnings`
+
+error: this `match` can be collapsed into the outer `match`
- --> $DIR/collapsible_match.rs:21:12
++ --> $DIR/collapsible_match.rs:22:20
+ |
+LL | Ok(val) => match val {
+ | ____________________^
+LL | | Some(n) => foo(n),
+LL | | _ => return,
+LL | | },
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:30:9
++ --> $DIR/collapsible_match.rs:22:12
+ |
+LL | Ok(val) => match val {
+ | ^^^ replace this binding
+LL | Some(n) => foo(n),
+ | ^^^^^^^ with this pattern
+
+error: this `if let` can be collapsed into the outer `if let`
- --> $DIR/collapsible_match.rs:29:15
++ --> $DIR/collapsible_match.rs:31:9
+ |
+LL | / if let Some(n) = val {
+LL | | take(n);
+LL | | }
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:37:9
++ --> $DIR/collapsible_match.rs:30:15
+ |
+LL | if let Ok(val) = res_opt {
+ | ^^^ replace this binding
+LL | if let Some(n) = val {
+ | ^^^^^^^ with this pattern
+
+error: this `if let` can be collapsed into the outer `if let`
- --> $DIR/collapsible_match.rs:36:15
++ --> $DIR/collapsible_match.rs:38:9
+ |
+LL | / if let Some(n) = val {
+LL | | take(n);
+LL | | } else {
+LL | | return;
+LL | | }
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:48:9
++ --> $DIR/collapsible_match.rs:37:15
+ |
+LL | if let Ok(val) = res_opt {
+ | ^^^ replace this binding
+LL | if let Some(n) = val {
+ | ^^^^^^^ with this pattern
+
+error: this `match` can be collapsed into the outer `if let`
- --> $DIR/collapsible_match.rs:47:15
++ --> $DIR/collapsible_match.rs:49:9
+ |
+LL | / match val {
+LL | | Some(n) => foo(n),
+LL | | _ => (),
+LL | | }
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:57:13
++ --> $DIR/collapsible_match.rs:48:15
+ |
+LL | if let Ok(val) = res_opt {
+ | ^^^ replace this binding
+LL | match val {
+LL | Some(n) => foo(n),
+ | ^^^^^^^ with this pattern
+
+error: this `if let` can be collapsed into the outer `match`
- --> $DIR/collapsible_match.rs:56:12
++ --> $DIR/collapsible_match.rs:58:13
+ |
+LL | / if let Some(n) = val {
+LL | | take(n);
+LL | | }
+ | |_____________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:66:9
++ --> $DIR/collapsible_match.rs:57:12
+ |
+LL | Ok(val) => {
+ | ^^^ replace this binding
+LL | if let Some(n) = val {
+ | ^^^^^^^ with this pattern
+
+error: this `match` can be collapsed into the outer `if let`
- --> $DIR/collapsible_match.rs:65:15
++ --> $DIR/collapsible_match.rs:67:9
+ |
+LL | / match val {
+LL | | Some(n) => foo(n),
+LL | | _ => return,
+LL | | }
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:77:13
++ --> $DIR/collapsible_match.rs:66:15
+ |
+LL | if let Ok(val) = res_opt {
+ | ^^^ replace this binding
+LL | match val {
+LL | Some(n) => foo(n),
+ | ^^^^^^^ with this pattern
+
+error: this `if let` can be collapsed into the outer `match`
- --> $DIR/collapsible_match.rs:76:12
++ --> $DIR/collapsible_match.rs:78:13
+ |
+LL | / if let Some(n) = val {
+LL | | take(n);
+LL | | } else {
+LL | | return;
+LL | | }
+ | |_____________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:88:20
++ --> $DIR/collapsible_match.rs:77:12
+ |
+LL | Ok(val) => {
+ | ^^^ replace this binding
+LL | if let Some(n) = val {
+ | ^^^^^^^ with this pattern
+
+error: this `match` can be collapsed into the outer `match`
- --> $DIR/collapsible_match.rs:88:12
++ --> $DIR/collapsible_match.rs:89:20
+ |
+LL | Ok(val) => match val {
+ | ____________________^
+LL | | Some(n) => foo(n),
+LL | | None => return,
+LL | | },
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
- --> $DIR/collapsible_match.rs:97:22
++ --> $DIR/collapsible_match.rs:89:12
+ |
+LL | Ok(val) => match val {
+ | ^^^ replace this binding
+LL | Some(n) => foo(n),
+ | ^^^^^^^ with this pattern
+
+error: this `match` can be collapsed into the outer `match`
- --> $DIR/collapsible_match.rs:97:14
++ --> $DIR/collapsible_match.rs:98:22
+ |
+LL | Some(val) => match val {
+ | ______________________^
+LL | | Some(n) => foo(n),
+LL | | _ => return,
+LL | | },
+ | |_________^
+ |
+help: the outer pattern can be modified to include the inner pattern
++ --> $DIR/collapsible_match.rs:98:14
+ |
+LL | Some(val) => match val {
+ | ^^^ replace this binding
+LL | Some(n) => foo(n),
+ | ^^^^^^^ with this pattern
+
+error: aborting due to 10 previous errors
+
--- /dev/null
++#![allow(clippy::uninlined_format_args)]
++
+pub struct ArrayWrapper<const N: usize>([usize; N]);
+
+impl<const N: usize> ArrayWrapper<{ N }> {
+ pub fn ice(&self) {
+ for i in self.0.iter() {
+ println!("{}", i);
+ }
+ }
+}
+
+fn main() {}
--- /dev/null
--- /dev/null
++const UNINIT: core::mem::MaybeUninit<core::cell::Cell<&'static ()>> = core::mem::MaybeUninit::uninit();
++
++fn main() {}
--- /dev/null
--- /dev/null
++#![feature(unsized_fn_params)]
++
++pub fn f0(_f: dyn FnOnce()) {}
++
++fn main() {}
--- /dev/null
- #![allow(clippy::disallowed_names)]
++#![allow(clippy::disallowed_names, clippy::uninlined_format_args)]
+
+pub fn foo(bar: *const u8) {
+ println!("{:#p}", bar);
+}
+
+// Regression test for https://github.com/rust-lang/rust-clippy/issues/4917
+/// <foo
+struct A;
+
+fn main() {}
--- /dev/null
-
- #![allow(unused_imports, dead_code)]
+// run-rustfix
+// aux-build: proc_macro_with_span.rs
+#![deny(clippy::default_trait_access)]
++#![allow(dead_code, unused_imports)]
++#![allow(clippy::uninlined_format_args)]
+
+extern crate proc_macro_with_span;
+
+use proc_macro_with_span::with_span;
+use std::default;
+use std::default::Default as D2;
+use std::string;
+
+fn main() {
+ let s1: String = std::string::String::default();
+
+ let s2 = String::default();
+
+ let s3: String = std::string::String::default();
+
+ let s4: String = std::string::String::default();
+
+ let s5 = string::String::default();
+
+ let s6: String = std::string::String::default();
+
+ let s7 = std::string::String::default();
+
+ let s8: String = DefaultFactory::make_t_badly();
+
+ let s9: String = DefaultFactory::make_t_nicely();
+
+ let s10 = DerivedDefault::default();
+
+ let s11: GenericDerivedDefault<String> = GenericDerivedDefault::default();
+
+ let s12 = GenericDerivedDefault::<String>::default();
+
+ let s13 = TupleDerivedDefault::default();
+
+ let s14: TupleDerivedDefault = TupleDerivedDefault::default();
+
+ let s15: ArrayDerivedDefault = ArrayDerivedDefault::default();
+
+ let s16 = ArrayDerivedDefault::default();
+
+ let s17: TupleStructDerivedDefault = TupleStructDerivedDefault::default();
+
+ let s18 = TupleStructDerivedDefault::default();
+
+ let s19 = <DerivedDefault as Default>::default();
+
+ let s20 = UpdateSyntax {
+ s: "foo",
+ ..Default::default()
+ };
+
+ let _s21: String = with_span!(s Default::default());
+
+ println!(
+ "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]",
+ s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20,
+ );
+}
+
+struct DefaultFactory;
+
+impl DefaultFactory {
+ pub fn make_t_badly<T: Default>() -> T {
+ Default::default()
+ }
+
+ pub fn make_t_nicely<T: Default>() -> T {
+ T::default()
+ }
+}
+
+#[derive(Debug, Default)]
+struct DerivedDefault {
+ pub s: String,
+}
+
+#[derive(Debug, Default)]
+struct GenericDerivedDefault<T: Default + std::fmt::Debug> {
+ pub s: T,
+}
+
+#[derive(Debug, Default)]
+struct TupleDerivedDefault {
+ pub s: (String, String),
+}
+
+#[derive(Debug, Default)]
+struct ArrayDerivedDefault {
+ pub s: [String; 10],
+}
+
+#[derive(Debug, Default)]
+struct TupleStructDerivedDefault(String);
+
+#[derive(Debug, Default)]
+struct UpdateSyntax {
+ pub s: &'static str,
+ pub u: u64,
+}
--- /dev/null
-
- #![allow(unused_imports, dead_code)]
+// run-rustfix
+// aux-build: proc_macro_with_span.rs
+#![deny(clippy::default_trait_access)]
++#![allow(dead_code, unused_imports)]
++#![allow(clippy::uninlined_format_args)]
+
+extern crate proc_macro_with_span;
+
+use proc_macro_with_span::with_span;
+use std::default;
+use std::default::Default as D2;
+use std::string;
+
+fn main() {
+ let s1: String = Default::default();
+
+ let s2 = String::default();
+
+ let s3: String = D2::default();
+
+ let s4: String = std::default::Default::default();
+
+ let s5 = string::String::default();
+
+ let s6: String = default::Default::default();
+
+ let s7 = std::string::String::default();
+
+ let s8: String = DefaultFactory::make_t_badly();
+
+ let s9: String = DefaultFactory::make_t_nicely();
+
+ let s10 = DerivedDefault::default();
+
+ let s11: GenericDerivedDefault<String> = Default::default();
+
+ let s12 = GenericDerivedDefault::<String>::default();
+
+ let s13 = TupleDerivedDefault::default();
+
+ let s14: TupleDerivedDefault = Default::default();
+
+ let s15: ArrayDerivedDefault = Default::default();
+
+ let s16 = ArrayDerivedDefault::default();
+
+ let s17: TupleStructDerivedDefault = Default::default();
+
+ let s18 = TupleStructDerivedDefault::default();
+
+ let s19 = <DerivedDefault as Default>::default();
+
+ let s20 = UpdateSyntax {
+ s: "foo",
+ ..Default::default()
+ };
+
+ let _s21: String = with_span!(s Default::default());
+
+ println!(
+ "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]",
+ s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20,
+ );
+}
+
+struct DefaultFactory;
+
+impl DefaultFactory {
+ pub fn make_t_badly<T: Default>() -> T {
+ Default::default()
+ }
+
+ pub fn make_t_nicely<T: Default>() -> T {
+ T::default()
+ }
+}
+
+#[derive(Debug, Default)]
+struct DerivedDefault {
+ pub s: String,
+}
+
+#[derive(Debug, Default)]
+struct GenericDerivedDefault<T: Default + std::fmt::Debug> {
+ pub s: T,
+}
+
+#[derive(Debug, Default)]
+struct TupleDerivedDefault {
+ pub s: (String, String),
+}
+
+#[derive(Debug, Default)]
+struct ArrayDerivedDefault {
+ pub s: [String; 10],
+}
+
+#[derive(Debug, Default)]
+struct TupleStructDerivedDefault(String);
+
+#[derive(Debug, Default)]
+struct UpdateSyntax {
+ pub s: &'static str,
+ pub u: u64,
+}
--- /dev/null
- --> $DIR/default_trait_access.rs:5:9
+error: calling `std::string::String::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:15:22
+ |
+LL | let s1: String = Default::default();
+ | ^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()`
+ |
+note: the lint level is defined here
++ --> $DIR/default_trait_access.rs:3:9
+ |
+LL | #![deny(clippy::default_trait_access)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: calling `std::string::String::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:19:22
+ |
+LL | let s3: String = D2::default();
+ | ^^^^^^^^^^^^^ help: try: `std::string::String::default()`
+
+error: calling `std::string::String::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:21:22
+ |
+LL | let s4: String = std::default::Default::default();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()`
+
+error: calling `std::string::String::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:25:22
+ |
+LL | let s6: String = default::Default::default();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::string::String::default()`
+
+error: calling `GenericDerivedDefault::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:35:46
+ |
+LL | let s11: GenericDerivedDefault<String> = Default::default();
+ | ^^^^^^^^^^^^^^^^^^ help: try: `GenericDerivedDefault::default()`
+
+error: calling `TupleDerivedDefault::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:41:36
+ |
+LL | let s14: TupleDerivedDefault = Default::default();
+ | ^^^^^^^^^^^^^^^^^^ help: try: `TupleDerivedDefault::default()`
+
+error: calling `ArrayDerivedDefault::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:43:36
+ |
+LL | let s15: ArrayDerivedDefault = Default::default();
+ | ^^^^^^^^^^^^^^^^^^ help: try: `ArrayDerivedDefault::default()`
+
+error: calling `TupleStructDerivedDefault::default()` is more clear than this expression
+ --> $DIR/default_trait_access.rs:47:42
+ |
+LL | let s17: TupleStructDerivedDefault = Default::default();
+ | ^^^^^^^^^^^^^^^^^^ help: try: `TupleStructDerivedDefault::default()`
+
+error: aborting due to 8 previous errors
+
--- /dev/null
- /// Calls ['bar']
+#![warn(clippy::doc_link_with_quotes)]
+
+fn main() {
+ foo()
+}
+
++/// Calls ['bar'] uselessly
+pub fn foo() {
+ bar()
+}
+
++/// # Examples
++/// This demonstrates issue \#8961
++/// ```
++/// let _ = vec!['w', 'a', 't'];
++/// ```
+pub fn bar() {}
--- /dev/null
- --> $DIR/doc_link_with_quotes.rs:7:1
+error: possible intra-doc link using quotes instead of backticks
- LL | /// Calls ['bar']
- | ^^^^^^^^^^^^^^^^^
++ --> $DIR/doc_link_with_quotes.rs:7:12
+ |
++LL | /// Calls ['bar'] uselessly
++ | ^^^^^
+ |
+ = note: `-D clippy::doc-link-with-quotes` implied by `-D warnings`
+
+error: aborting due to previous error
+
--- /dev/null
+#![warn(clippy::drop_copy, clippy::forget_copy)]
+#![allow(clippy::toplevel_ref_arg, clippy::drop_ref, clippy::forget_ref, unused_mut)]
+
+use std::mem::{drop, forget};
+use std::vec::Vec;
+
+#[derive(Copy, Clone)]
+struct SomeStruct;
+
+struct AnotherStruct {
+ x: u8,
+ y: u8,
+ z: Vec<u8>,
+}
+
+impl Clone for AnotherStruct {
+ fn clone(&self) -> AnotherStruct {
+ AnotherStruct {
+ x: self.x,
+ y: self.y,
+ z: self.z.clone(),
+ }
+ }
+}
+
+fn main() {
+ let s1 = SomeStruct {};
+ let s2 = s1;
+ let s3 = &s1;
+ let mut s4 = s1;
+ let ref s5 = s1;
+
+ drop(s1);
+ drop(s2);
+ drop(s3);
+ drop(s4);
+ drop(s5);
+
+ forget(s1);
+ forget(s2);
+ forget(s3);
+ forget(s4);
+ forget(s5);
+
+ let a1 = AnotherStruct {
+ x: 255,
+ y: 0,
+ z: vec![1, 2, 3],
+ };
+ let a2 = &a1;
+ let mut a3 = a1.clone();
+ let ref a4 = a1;
+ let a5 = a1.clone();
+
+ drop(a2);
+ drop(a3);
+ drop(a4);
+ drop(a5);
+
+ forget(a2);
+ let a3 = &a1;
+ forget(a3);
+ forget(a4);
+ let a5 = a1.clone();
+ forget(a5);
+}
++
++#[allow(unused)]
++#[allow(clippy::unit_cmp)]
++fn issue9482(x: u8) {
++ fn println_and<T>(t: T) -> T {
++ println!("foo");
++ t
++ }
++
++ match x {
++ 0 => drop(println_and(12)), // Don't lint (copy type), we only care about side-effects
++ 1 => drop(println_and(String::new())), // Don't lint (no copy type), we only care about side-effects
++ 2 => {
++ drop(println_and(13)); // Lint, even if we only care about the side-effect, it's already in a block
++ },
++ 3 if drop(println_and(14)) == () => (), // Lint, idiomatic use is only in body of `Arm`
++ 4 => drop(2), // Lint, not a fn/method call
++ _ => (),
++ }
++}
--- /dev/null
- error: aborting due to 6 previous errors
+error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact
+ --> $DIR/drop_forget_copy.rs:33:5
+ |
+LL | drop(s1);
+ | ^^^^^^^^
+ |
+note: argument has type `SomeStruct`
+ --> $DIR/drop_forget_copy.rs:33:10
+ |
+LL | drop(s1);
+ | ^^
+ = note: `-D clippy::drop-copy` implied by `-D warnings`
+
+error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact
+ --> $DIR/drop_forget_copy.rs:34:5
+ |
+LL | drop(s2);
+ | ^^^^^^^^
+ |
+note: argument has type `SomeStruct`
+ --> $DIR/drop_forget_copy.rs:34:10
+ |
+LL | drop(s2);
+ | ^^
+
+error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact
+ --> $DIR/drop_forget_copy.rs:36:5
+ |
+LL | drop(s4);
+ | ^^^^^^^^
+ |
+note: argument has type `SomeStruct`
+ --> $DIR/drop_forget_copy.rs:36:10
+ |
+LL | drop(s4);
+ | ^^
+
+error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact
+ --> $DIR/drop_forget_copy.rs:39:5
+ |
+LL | forget(s1);
+ | ^^^^^^^^^^
+ |
+note: argument has type `SomeStruct`
+ --> $DIR/drop_forget_copy.rs:39:12
+ |
+LL | forget(s1);
+ | ^^
+ = note: `-D clippy::forget-copy` implied by `-D warnings`
+
+error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact
+ --> $DIR/drop_forget_copy.rs:40:5
+ |
+LL | forget(s2);
+ | ^^^^^^^^^^
+ |
+note: argument has type `SomeStruct`
+ --> $DIR/drop_forget_copy.rs:40:12
+ |
+LL | forget(s2);
+ | ^^
+
+error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetting a copy leaves the original intact
+ --> $DIR/drop_forget_copy.rs:42:5
+ |
+LL | forget(s4);
+ | ^^^^^^^^^^
+ |
+note: argument has type `SomeStruct`
+ --> $DIR/drop_forget_copy.rs:42:12
+ |
+LL | forget(s4);
+ | ^^
+
++error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact
++ --> $DIR/drop_forget_copy.rs:80:13
++ |
++LL | drop(println_and(13)); // Lint, even if we only care about the side-effect, it's already in a block
++ | ^^^^^^^^^^^^^^^^^^^^^
++ |
++note: argument has type `i32`
++ --> $DIR/drop_forget_copy.rs:80:18
++ |
++LL | drop(println_and(13)); // Lint, even if we only care about the side-effect, it's already in a block
++ | ^^^^^^^^^^^^^^^
++
++error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact
++ --> $DIR/drop_forget_copy.rs:82:14
++ |
++LL | 3 if drop(println_and(14)) == () => (), // Lint, idiomatic use is only in body of `Arm`
++ | ^^^^^^^^^^^^^^^^^^^^^
++ |
++note: argument has type `i32`
++ --> $DIR/drop_forget_copy.rs:82:19
++ |
++LL | 3 if drop(println_and(14)) == () => (), // Lint, idiomatic use is only in body of `Arm`
++ | ^^^^^^^^^^^^^^^
++
++error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a copy leaves the original intact
++ --> $DIR/drop_forget_copy.rs:83:14
++ |
++LL | 4 => drop(2), // Lint, not a fn/method call
++ | ^^^^^^^
++ |
++note: argument has type `i32`
++ --> $DIR/drop_forget_copy.rs:83:19
++ |
++LL | 4 => drop(2), // Lint, not a fn/method call
++ | ^
++
++error: aborting due to 9 previous errors
+
--- /dev/null
-
+// run-rustfix
- unused,
- clippy::no_effect,
- clippy::redundant_closure_call,
++#![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
++#![allow(unused)]
+#![allow(
- clippy::needless_borrow
++ clippy::needless_borrow,
+ clippy::needless_pass_by_value,
++ clippy::no_effect,
+ clippy::option_map_unit_fn,
- #![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
++ clippy::redundant_closure_call,
++ clippy::uninlined_format_args
+)]
+
+use std::path::{Path, PathBuf};
+
+macro_rules! mac {
+ () => {
+ foobar()
+ };
+}
+
+macro_rules! closure_mac {
+ () => {
+ |n| foo(n)
+ };
+}
+
+fn main() {
+ let a = Some(1u8).map(foo);
+ let c = Some(1u8).map(|a| {1+2; foo}(a));
+ true.then(|| mac!()); // don't lint function in macro expansion
+ Some(1).map(closure_mac!()); // don't lint closure in macro expansion
+ let _: Option<Vec<u8>> = true.then(std::vec::Vec::new); // special case vec!
+ let d = Some(1u8).map(|a| foo(foo2(a))); //is adjusted?
+ all(&[1, 2, 3], &&2, below); //is adjusted
+ unsafe {
+ Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn
+ }
+
+ // See #815
+ let e = Some(1u8).map(|a| divergent(a));
+ let e = Some(1u8).map(generic);
+ let e = Some(1u8).map(generic);
+ // See #515
+ let a: Option<Box<dyn (::std::ops::Deref<Target = [i32]>)>> =
+ Some(vec![1i32, 2]).map(|v| -> Box<dyn (::std::ops::Deref<Target = [i32]>)> { Box::new(v) });
+
+ // issue #7224
+ let _: Option<Vec<u32>> = Some(0).map(|_| vec![]);
+}
+
+trait TestTrait {
+ fn trait_foo(self) -> bool;
+ fn trait_foo_ref(&self) -> bool;
+}
+
+struct TestStruct<'a> {
+ some_ref: &'a i32,
+}
+
+impl<'a> TestStruct<'a> {
+ fn foo(self) -> bool {
+ false
+ }
+ unsafe fn foo_unsafe(self) -> bool {
+ true
+ }
+}
+
+impl<'a> TestTrait for TestStruct<'a> {
+ fn trait_foo(self) -> bool {
+ false
+ }
+ fn trait_foo_ref(&self) -> bool {
+ false
+ }
+}
+
+impl<'a> std::ops::Deref for TestStruct<'a> {
+ type Target = char;
+ fn deref(&self) -> &char {
+ &'a'
+ }
+}
+
+fn test_redundant_closures_containing_method_calls() {
+ let i = 10;
+ let e = Some(TestStruct { some_ref: &i }).map(TestStruct::foo);
+ let e = Some(TestStruct { some_ref: &i }).map(TestTrait::trait_foo);
+ let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo_ref());
+ let e = Some(&mut vec![1, 2, 3]).map(std::vec::Vec::clear);
+ unsafe {
+ let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo_unsafe());
+ }
+ let e = Some("str").map(std::string::ToString::to_string);
+ let e = Some('a').map(char::to_uppercase);
+ let e: std::vec::Vec<usize> = vec!['a', 'b', 'c'].iter().map(|c| c.len_utf8()).collect();
+ let e: std::vec::Vec<char> = vec!['a', 'b', 'c'].iter().map(char::to_ascii_uppercase).collect();
+ let e = Some(PathBuf::new()).as_ref().and_then(|s| s.to_str());
+ let c = Some(TestStruct { some_ref: &i })
+ .as_ref()
+ .map(|c| c.to_ascii_uppercase());
+
+ fn test_different_borrow_levels<T>(t: &[&T])
+ where
+ T: TestTrait,
+ {
+ t.iter().filter(|x| x.trait_foo_ref());
+ t.iter().map(|x| x.trait_foo_ref());
+ }
+}
+
+struct Thunk<T>(Box<dyn FnMut() -> T>);
+
+impl<T> Thunk<T> {
+ fn new<F: 'static + FnOnce() -> T>(f: F) -> Thunk<T> {
+ let mut option = Some(f);
+ // This should not trigger redundant_closure (#1439)
+ Thunk(Box::new(move || option.take().unwrap()()))
+ }
+
+ fn unwrap(self) -> T {
+ let Thunk(mut f) = self;
+ f()
+ }
+}
+
+fn foobar() {
+ let thunk = Thunk::new(|| println!("Hello, world!"));
+ thunk.unwrap()
+}
+
+fn foo(_: u8) {}
+
+fn foo2(_: u8) -> u8 {
+ 1u8
+}
+
+fn all<X, F>(x: &[X], y: &X, f: F) -> bool
+where
+ F: Fn(&X, &X) -> bool,
+{
+ x.iter().all(|e| f(e, y))
+}
+
+fn below(x: &u8, y: &u8) -> bool {
+ x < y
+}
+
+unsafe fn unsafe_fn(_: u8) {}
+
+fn divergent(_: u8) -> ! {
+ unimplemented!()
+}
+
+fn generic<T>(_: T) -> u8 {
+ 0
+}
+
+fn passes_fn_mut(mut x: Box<dyn FnMut()>) {
+ requires_fn_once(x);
+}
+fn requires_fn_once<T: FnOnce()>(_: T) {}
+
+fn test_redundant_closure_with_function_pointer() {
+ type FnPtrType = fn(u8);
+ let foo_ptr: FnPtrType = foo;
+ let a = Some(1u8).map(foo_ptr);
+}
+
+fn test_redundant_closure_with_another_closure() {
+ let closure = |a| println!("{}", a);
+ let a = Some(1u8).map(closure);
+}
+
+fn make_lazy(f: impl Fn() -> fn(u8) -> u8) -> impl Fn(u8) -> u8 {
+ // Currently f is called when result of make_lazy is called.
+ // If the closure is removed, f will be called when make_lazy itself is
+ // called. This changes semantics, so the closure must stay.
+ Box::new(move |x| f()(x))
+}
+
+fn call<F: FnOnce(&mut String) -> String>(f: F) -> String {
+ f(&mut "Hello".to_owned())
+}
+fn test_difference_in_mutability() {
+ call(|s| s.clone());
+}
+
+struct Bar;
+impl std::ops::Deref for Bar {
+ type Target = str;
+ fn deref(&self) -> &str {
+ "hi"
+ }
+}
+
+fn test_deref_with_trait_method() {
+ let _ = [Bar].iter().map(|s| s.to_string()).collect::<Vec<_>>();
+}
+
+fn mutable_closure_used_again(x: Vec<i32>, y: Vec<i32>, z: Vec<i32>) {
+ let mut res = Vec::new();
+ let mut add_to_res = |n| res.push(n);
+ x.into_iter().for_each(&mut add_to_res);
+ y.into_iter().for_each(&mut add_to_res);
+ z.into_iter().for_each(add_to_res);
+}
+
+fn mutable_closure_in_loop() {
+ let mut value = 0;
+ let mut closure = |n| value += n;
+ for _ in 0..5 {
+ Some(1).map(&mut closure);
+
+ let mut value = 0;
+ let mut in_loop = |n| value += n;
+ Some(1).map(in_loop);
+ }
+}
+
+fn late_bound_lifetimes() {
+ fn take_asref_path<P: AsRef<Path>>(path: P) {}
+
+ fn map_str<F>(thunk: F)
+ where
+ F: FnOnce(&str),
+ {
+ }
+
+ fn map_str_to_path<F>(thunk: F)
+ where
+ F: FnOnce(&str) -> &Path,
+ {
+ }
+ map_str(|s| take_asref_path(s));
+ map_str_to_path(|s| s.as_ref());
+}
+
+mod type_param_bound {
+ trait Trait {
+ fn fun();
+ }
+
+ fn take<T: 'static>(_: T) {}
+
+ fn test<X: Trait>() {
+ // don't lint, but it's questionable that rust requires a cast
+ take(|| X::fun());
+ take(X::fun as fn());
+ }
+}
+
+// #8073 Don't replace closure with `Arc<F>` or `Rc<F>`
+fn arc_fp() {
+ let rc = std::rc::Rc::new(|| 7);
+ let arc = std::sync::Arc::new(|n| n + 1);
+ let ref_arc = &std::sync::Arc::new(|_| 5);
+
+ true.then(|| rc());
+ (0..5).map(|n| arc(n));
+ Some(4).map(|n| ref_arc(n));
+}
+
+// #8460 Don't replace closures with params bounded as `ref`
+mod bind_by_ref {
+ struct A;
+ struct B;
+
+ impl From<&A> for B {
+ fn from(A: &A) -> Self {
+ B
+ }
+ }
+
+ fn test() {
+ // should not lint
+ Some(A).map(|a| B::from(&a));
+ // should not lint
+ Some(A).map(|ref a| B::from(a));
+ }
+}
+
+// #7812 False positive on coerced closure
+fn coerced_closure() {
+ fn function_returning_unit<F: FnMut(i32)>(f: F) {}
+ function_returning_unit(|x| std::process::exit(x));
+
+ fn arr() -> &'static [u8; 0] {
+ &[]
+ }
+ fn slice_fn(_: impl FnOnce() -> &'static [u8]) {}
+ slice_fn(|| arr());
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/7861
+fn box_dyn() {
+ fn f(_: impl Fn(usize) -> Box<dyn std::any::Any>) {}
+ f(|x| Box::new(x));
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/5939
+fn not_general_enough() {
+ fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {}
+ f(|path| std::fs::remove_file(path));
+}
++
++// https://github.com/rust-lang/rust-clippy/issues/9369
++pub fn mutable_impl_fn_mut(mut f: impl FnMut(), mut f_used_once: impl FnMut()) -> impl FnMut() {
++ fn takes_fn_mut(_: impl FnMut()) {}
++ takes_fn_mut(&mut f);
++
++ fn takes_fn_once(_: impl FnOnce()) {}
++ takes_fn_once(&mut f);
++
++ f();
++
++ move || takes_fn_mut(&mut f_used_once)
++}
--- /dev/null
-
+// run-rustfix
- unused,
- clippy::no_effect,
- clippy::redundant_closure_call,
++#![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
++#![allow(unused)]
+#![allow(
- clippy::needless_borrow
++ clippy::needless_borrow,
+ clippy::needless_pass_by_value,
++ clippy::no_effect,
+ clippy::option_map_unit_fn,
- #![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)]
++ clippy::redundant_closure_call,
++ clippy::uninlined_format_args
+)]
+
+use std::path::{Path, PathBuf};
+
+macro_rules! mac {
+ () => {
+ foobar()
+ };
+}
+
+macro_rules! closure_mac {
+ () => {
+ |n| foo(n)
+ };
+}
+
+fn main() {
+ let a = Some(1u8).map(|a| foo(a));
+ let c = Some(1u8).map(|a| {1+2; foo}(a));
+ true.then(|| mac!()); // don't lint function in macro expansion
+ Some(1).map(closure_mac!()); // don't lint closure in macro expansion
+ let _: Option<Vec<u8>> = true.then(|| vec![]); // special case vec!
+ let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted?
+ all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted
+ unsafe {
+ Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn
+ }
+
+ // See #815
+ let e = Some(1u8).map(|a| divergent(a));
+ let e = Some(1u8).map(|a| generic(a));
+ let e = Some(1u8).map(generic);
+ // See #515
+ let a: Option<Box<dyn (::std::ops::Deref<Target = [i32]>)>> =
+ Some(vec![1i32, 2]).map(|v| -> Box<dyn (::std::ops::Deref<Target = [i32]>)> { Box::new(v) });
+
+ // issue #7224
+ let _: Option<Vec<u32>> = Some(0).map(|_| vec![]);
+}
+
+trait TestTrait {
+ fn trait_foo(self) -> bool;
+ fn trait_foo_ref(&self) -> bool;
+}
+
+struct TestStruct<'a> {
+ some_ref: &'a i32,
+}
+
+impl<'a> TestStruct<'a> {
+ fn foo(self) -> bool {
+ false
+ }
+ unsafe fn foo_unsafe(self) -> bool {
+ true
+ }
+}
+
+impl<'a> TestTrait for TestStruct<'a> {
+ fn trait_foo(self) -> bool {
+ false
+ }
+ fn trait_foo_ref(&self) -> bool {
+ false
+ }
+}
+
+impl<'a> std::ops::Deref for TestStruct<'a> {
+ type Target = char;
+ fn deref(&self) -> &char {
+ &'a'
+ }
+}
+
+fn test_redundant_closures_containing_method_calls() {
+ let i = 10;
+ let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo());
+ let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo());
+ let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo_ref());
+ let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear());
+ unsafe {
+ let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo_unsafe());
+ }
+ let e = Some("str").map(|s| s.to_string());
+ let e = Some('a').map(|s| s.to_uppercase());
+ let e: std::vec::Vec<usize> = vec!['a', 'b', 'c'].iter().map(|c| c.len_utf8()).collect();
+ let e: std::vec::Vec<char> = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect();
+ let e = Some(PathBuf::new()).as_ref().and_then(|s| s.to_str());
+ let c = Some(TestStruct { some_ref: &i })
+ .as_ref()
+ .map(|c| c.to_ascii_uppercase());
+
+ fn test_different_borrow_levels<T>(t: &[&T])
+ where
+ T: TestTrait,
+ {
+ t.iter().filter(|x| x.trait_foo_ref());
+ t.iter().map(|x| x.trait_foo_ref());
+ }
+}
+
+struct Thunk<T>(Box<dyn FnMut() -> T>);
+
+impl<T> Thunk<T> {
+ fn new<F: 'static + FnOnce() -> T>(f: F) -> Thunk<T> {
+ let mut option = Some(f);
+ // This should not trigger redundant_closure (#1439)
+ Thunk(Box::new(move || option.take().unwrap()()))
+ }
+
+ fn unwrap(self) -> T {
+ let Thunk(mut f) = self;
+ f()
+ }
+}
+
+fn foobar() {
+ let thunk = Thunk::new(|| println!("Hello, world!"));
+ thunk.unwrap()
+}
+
+fn foo(_: u8) {}
+
+fn foo2(_: u8) -> u8 {
+ 1u8
+}
+
+fn all<X, F>(x: &[X], y: &X, f: F) -> bool
+where
+ F: Fn(&X, &X) -> bool,
+{
+ x.iter().all(|e| f(e, y))
+}
+
+fn below(x: &u8, y: &u8) -> bool {
+ x < y
+}
+
+unsafe fn unsafe_fn(_: u8) {}
+
+fn divergent(_: u8) -> ! {
+ unimplemented!()
+}
+
+fn generic<T>(_: T) -> u8 {
+ 0
+}
+
+fn passes_fn_mut(mut x: Box<dyn FnMut()>) {
+ requires_fn_once(|| x());
+}
+fn requires_fn_once<T: FnOnce()>(_: T) {}
+
+fn test_redundant_closure_with_function_pointer() {
+ type FnPtrType = fn(u8);
+ let foo_ptr: FnPtrType = foo;
+ let a = Some(1u8).map(|a| foo_ptr(a));
+}
+
+fn test_redundant_closure_with_another_closure() {
+ let closure = |a| println!("{}", a);
+ let a = Some(1u8).map(|a| closure(a));
+}
+
+fn make_lazy(f: impl Fn() -> fn(u8) -> u8) -> impl Fn(u8) -> u8 {
+ // Currently f is called when result of make_lazy is called.
+ // If the closure is removed, f will be called when make_lazy itself is
+ // called. This changes semantics, so the closure must stay.
+ Box::new(move |x| f()(x))
+}
+
+fn call<F: FnOnce(&mut String) -> String>(f: F) -> String {
+ f(&mut "Hello".to_owned())
+}
+fn test_difference_in_mutability() {
+ call(|s| s.clone());
+}
+
+struct Bar;
+impl std::ops::Deref for Bar {
+ type Target = str;
+ fn deref(&self) -> &str {
+ "hi"
+ }
+}
+
+fn test_deref_with_trait_method() {
+ let _ = [Bar].iter().map(|s| s.to_string()).collect::<Vec<_>>();
+}
+
+fn mutable_closure_used_again(x: Vec<i32>, y: Vec<i32>, z: Vec<i32>) {
+ let mut res = Vec::new();
+ let mut add_to_res = |n| res.push(n);
+ x.into_iter().for_each(|x| add_to_res(x));
+ y.into_iter().for_each(|x| add_to_res(x));
+ z.into_iter().for_each(|x| add_to_res(x));
+}
+
+fn mutable_closure_in_loop() {
+ let mut value = 0;
+ let mut closure = |n| value += n;
+ for _ in 0..5 {
+ Some(1).map(|n| closure(n));
+
+ let mut value = 0;
+ let mut in_loop = |n| value += n;
+ Some(1).map(|n| in_loop(n));
+ }
+}
+
+fn late_bound_lifetimes() {
+ fn take_asref_path<P: AsRef<Path>>(path: P) {}
+
+ fn map_str<F>(thunk: F)
+ where
+ F: FnOnce(&str),
+ {
+ }
+
+ fn map_str_to_path<F>(thunk: F)
+ where
+ F: FnOnce(&str) -> &Path,
+ {
+ }
+ map_str(|s| take_asref_path(s));
+ map_str_to_path(|s| s.as_ref());
+}
+
+mod type_param_bound {
+ trait Trait {
+ fn fun();
+ }
+
+ fn take<T: 'static>(_: T) {}
+
+ fn test<X: Trait>() {
+ // don't lint, but it's questionable that rust requires a cast
+ take(|| X::fun());
+ take(X::fun as fn());
+ }
+}
+
+// #8073 Don't replace closure with `Arc<F>` or `Rc<F>`
+fn arc_fp() {
+ let rc = std::rc::Rc::new(|| 7);
+ let arc = std::sync::Arc::new(|n| n + 1);
+ let ref_arc = &std::sync::Arc::new(|_| 5);
+
+ true.then(|| rc());
+ (0..5).map(|n| arc(n));
+ Some(4).map(|n| ref_arc(n));
+}
+
+// #8460 Don't replace closures with params bounded as `ref`
+mod bind_by_ref {
+ struct A;
+ struct B;
+
+ impl From<&A> for B {
+ fn from(A: &A) -> Self {
+ B
+ }
+ }
+
+ fn test() {
+ // should not lint
+ Some(A).map(|a| B::from(&a));
+ // should not lint
+ Some(A).map(|ref a| B::from(a));
+ }
+}
+
+// #7812 False positive on coerced closure
+fn coerced_closure() {
+ fn function_returning_unit<F: FnMut(i32)>(f: F) {}
+ function_returning_unit(|x| std::process::exit(x));
+
+ fn arr() -> &'static [u8; 0] {
+ &[]
+ }
+ fn slice_fn(_: impl FnOnce() -> &'static [u8]) {}
+ slice_fn(|| arr());
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/7861
+fn box_dyn() {
+ fn f(_: impl Fn(usize) -> Box<dyn std::any::Any>) {}
+ f(|x| Box::new(x));
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/5939
+fn not_general_enough() {
+ fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {}
+ f(|path| std::fs::remove_file(path));
+}
++
++// https://github.com/rust-lang/rust-clippy/issues/9369
++pub fn mutable_impl_fn_mut(mut f: impl FnMut(), mut f_used_once: impl FnMut()) -> impl FnMut() {
++ fn takes_fn_mut(_: impl FnMut()) {}
++ takes_fn_mut(|| f());
++
++ fn takes_fn_once(_: impl FnOnce()) {}
++ takes_fn_once(|| f());
++
++ f();
++
++ move || takes_fn_mut(|| f_used_once())
++}
--- /dev/null
- error: aborting due to 19 previous errors
+error: redundant closure
+ --> $DIR/eta.rs:28:27
+ |
+LL | let a = Some(1u8).map(|a| foo(a));
+ | ^^^^^^^^^^ help: replace the closure with the function itself: `foo`
+ |
+ = note: `-D clippy::redundant-closure` implied by `-D warnings`
+
+error: redundant closure
+ --> $DIR/eta.rs:32:40
+ |
+LL | let _: Option<Vec<u8>> = true.then(|| vec![]); // special case vec!
+ | ^^^^^^^^^ help: replace the closure with `Vec::new`: `std::vec::Vec::new`
+
+error: redundant closure
+ --> $DIR/eta.rs:33:35
+ |
+LL | let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted?
+ | ^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo2`
+
+error: redundant closure
+ --> $DIR/eta.rs:34:26
+ |
+LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted
+ | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `below`
+
+error: redundant closure
+ --> $DIR/eta.rs:41:27
+ |
+LL | let e = Some(1u8).map(|a| generic(a));
+ | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `generic`
+
+error: redundant closure
+ --> $DIR/eta.rs:87:51
+ |
+LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo());
+ | ^^^^^^^^^^^ help: replace the closure with the method itself: `TestStruct::foo`
+ |
+ = note: `-D clippy::redundant-closure-for-method-calls` implied by `-D warnings`
+
+error: redundant closure
+ --> $DIR/eta.rs:88:51
+ |
+LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo());
+ | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `TestTrait::trait_foo`
+
+error: redundant closure
+ --> $DIR/eta.rs:90:42
+ |
+LL | let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear());
+ | ^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::vec::Vec::clear`
+
+error: redundant closure
+ --> $DIR/eta.rs:94:29
+ |
+LL | let e = Some("str").map(|s| s.to_string());
+ | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string`
+
+error: redundant closure
+ --> $DIR/eta.rs:95:27
+ |
+LL | let e = Some('a').map(|s| s.to_uppercase());
+ | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_uppercase`
+
+error: redundant closure
+ --> $DIR/eta.rs:97:65
+ |
+LL | let e: std::vec::Vec<char> = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase`
+
+error: redundant closure
+ --> $DIR/eta.rs:160:22
+ |
+LL | requires_fn_once(|| x());
+ | ^^^^^^ help: replace the closure with the function itself: `x`
+
+error: redundant closure
+ --> $DIR/eta.rs:167:27
+ |
+LL | let a = Some(1u8).map(|a| foo_ptr(a));
+ | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr`
+
+error: redundant closure
+ --> $DIR/eta.rs:172:27
+ |
+LL | let a = Some(1u8).map(|a| closure(a));
+ | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure`
+
+error: redundant closure
+ --> $DIR/eta.rs:204:28
+ |
+LL | x.into_iter().for_each(|x| add_to_res(x));
+ | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res`
+
+error: redundant closure
+ --> $DIR/eta.rs:205:28
+ |
+LL | y.into_iter().for_each(|x| add_to_res(x));
+ | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res`
+
+error: redundant closure
+ --> $DIR/eta.rs:206:28
+ |
+LL | z.into_iter().for_each(|x| add_to_res(x));
+ | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res`
+
+error: redundant closure
+ --> $DIR/eta.rs:213:21
+ |
+LL | Some(1).map(|n| closure(n));
+ | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure`
+
+error: redundant closure
+ --> $DIR/eta.rs:217:21
+ |
+LL | Some(1).map(|n| in_loop(n));
+ | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `in_loop`
+
++error: redundant closure
++ --> $DIR/eta.rs:310:18
++ |
++LL | takes_fn_mut(|| f());
++ | ^^^^^^ help: replace the closure with the function itself: `&mut f`
++
++error: redundant closure
++ --> $DIR/eta.rs:313:19
++ |
++LL | takes_fn_once(|| f());
++ | ^^^^^^ help: replace the closure with the function itself: `&mut f`
++
++error: redundant closure
++ --> $DIR/eta.rs:317:26
++ |
++LL | move || takes_fn_mut(|| f_used_once())
++ | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut f_used_once`
++
++error: aborting due to 22 previous errors
+
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::to_string_in_format_args)]
+#![warn(clippy::expect_fun_call)]
++#![allow(clippy::to_string_in_format_args, clippy::uninlined_format_args)]
+
+/// Checks implementation of the `EXPECT_FUN_CALL` lint
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Self {
+ Foo
+ }
+
+ fn expect(&self, msg: &str) {
+ panic!("{}", msg)
+ }
+ }
+
+ let with_some = Some("value");
+ with_some.expect("error");
+
+ let with_none: Option<i32> = None;
+ with_none.expect("error");
+
+ let error_code = 123_i32;
+ let with_none_and_format: Option<i32> = None;
+ with_none_and_format.unwrap_or_else(|| panic!("Error {}: fake error", error_code));
+
+ let with_none_and_as_str: Option<i32> = None;
+ with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code));
+
+ let with_none_and_format_with_macro: Option<i32> = None;
+ with_none_and_format_with_macro.unwrap_or_else(|| panic!("Error {}: fake error", one!()));
+
+ let with_ok: Result<(), ()> = Ok(());
+ with_ok.expect("error");
+
+ let with_err: Result<(), ()> = Err(());
+ with_err.expect("error");
+
+ let error_code = 123_i32;
+ let with_err_and_format: Result<(), ()> = Err(());
+ with_err_and_format.unwrap_or_else(|_| panic!("Error {}: fake error", error_code));
+
+ let with_err_and_as_str: Result<(), ()> = Err(());
+ with_err_and_as_str.unwrap_or_else(|_| panic!("Error {}: fake error", error_code));
+
+ let with_dummy_type = Foo::new();
+ with_dummy_type.expect("another test string");
+
+ let with_dummy_type_and_format = Foo::new();
+ with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code));
+
+ let with_dummy_type_and_as_str = Foo::new();
+ with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
+
+ //Issue #2937
+ Some("foo").unwrap_or_else(|| panic!("{} {}", 1, 2));
+
+ //Issue #2979 - this should not lint
+ {
+ let msg = "bar";
+ Some("foo").expect(msg);
+ }
+
+ {
+ fn get_string() -> String {
+ "foo".to_string()
+ }
+
+ fn get_static_str() -> &'static str {
+ "foo"
+ }
+
+ fn get_non_static_str(_: &u32) -> &str {
+ "foo"
+ }
+
+ Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
+ Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
+ Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) });
+
+ Some("foo").unwrap_or_else(|| { panic!("{}", get_static_str()) });
+ Some("foo").unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) });
+ }
+
+ //Issue #3839
+ Some(true).unwrap_or_else(|| panic!("key {}, {}", 1, 2));
+
+ //Issue #4912 - the receiver is a &Option
+ {
+ let opt = Some(1);
+ let opt_ref = &opt;
+ opt_ref.unwrap_or_else(|| panic!("{:?}", opt_ref));
+ }
++
++ let format_capture: Option<i32> = None;
++ format_capture.unwrap_or_else(|| panic!("{error_code}"));
++
++ let format_capture_and_value: Option<i32> = None;
++ format_capture_and_value.unwrap_or_else(|| panic!("{error_code}, {}", 1));
+}
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::to_string_in_format_args)]
+#![warn(clippy::expect_fun_call)]
++#![allow(clippy::to_string_in_format_args, clippy::uninlined_format_args)]
+
+/// Checks implementation of the `EXPECT_FUN_CALL` lint
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Self {
+ Foo
+ }
+
+ fn expect(&self, msg: &str) {
+ panic!("{}", msg)
+ }
+ }
+
+ let with_some = Some("value");
+ with_some.expect("error");
+
+ let with_none: Option<i32> = None;
+ with_none.expect("error");
+
+ let error_code = 123_i32;
+ let with_none_and_format: Option<i32> = None;
+ with_none_and_format.expect(&format!("Error {}: fake error", error_code));
+
+ let with_none_and_as_str: Option<i32> = None;
+ with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
+
+ let with_none_and_format_with_macro: Option<i32> = None;
+ with_none_and_format_with_macro.expect(format!("Error {}: fake error", one!()).as_str());
+
+ let with_ok: Result<(), ()> = Ok(());
+ with_ok.expect("error");
+
+ let with_err: Result<(), ()> = Err(());
+ with_err.expect("error");
+
+ let error_code = 123_i32;
+ let with_err_and_format: Result<(), ()> = Err(());
+ with_err_and_format.expect(&format!("Error {}: fake error", error_code));
+
+ let with_err_and_as_str: Result<(), ()> = Err(());
+ with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
+
+ let with_dummy_type = Foo::new();
+ with_dummy_type.expect("another test string");
+
+ let with_dummy_type_and_format = Foo::new();
+ with_dummy_type_and_format.expect(&format!("Error {}: fake error", error_code));
+
+ let with_dummy_type_and_as_str = Foo::new();
+ with_dummy_type_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
+
+ //Issue #2937
+ Some("foo").expect(format!("{} {}", 1, 2).as_ref());
+
+ //Issue #2979 - this should not lint
+ {
+ let msg = "bar";
+ Some("foo").expect(msg);
+ }
+
+ {
+ fn get_string() -> String {
+ "foo".to_string()
+ }
+
+ fn get_static_str() -> &'static str {
+ "foo"
+ }
+
+ fn get_non_static_str(_: &u32) -> &str {
+ "foo"
+ }
+
+ Some("foo").expect(&get_string());
+ Some("foo").expect(get_string().as_ref());
+ Some("foo").expect(get_string().as_str());
+
+ Some("foo").expect(get_static_str());
+ Some("foo").expect(get_non_static_str(&0));
+ }
+
+ //Issue #3839
+ Some(true).expect(&format!("key {}, {}", 1, 2));
+
+ //Issue #4912 - the receiver is a &Option
+ {
+ let opt = Some(1);
+ let opt_ref = &opt;
+ opt_ref.expect(&format!("{:?}", opt_ref));
+ }
++
++ let format_capture: Option<i32> = None;
++ format_capture.expect(&format!("{error_code}"));
++
++ let format_capture_and_value: Option<i32> = None;
++ format_capture_and_value.expect(&format!("{error_code}, {}", 1));
+}
--- /dev/null
- --> $DIR/expect_fun_call.rs:35:26
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:38:26
++ --> $DIR/expect_fun_call.rs:34:26
+ |
+LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
+ |
+ = note: `-D clippy::expect-fun-call` implied by `-D warnings`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:41:37
++ --> $DIR/expect_fun_call.rs:37:26
+ |
+LL | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:51:25
++ --> $DIR/expect_fun_call.rs:40:37
+ |
+LL | with_none_and_format_with_macro.expect(format!("Error {}: fake error", one!()).as_str());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", one!()))`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:54:25
++ --> $DIR/expect_fun_call.rs:50:25
+ |
+LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:66:17
++ --> $DIR/expect_fun_call.rs:53:25
+ |
+LL | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:87:21
++ --> $DIR/expect_fun_call.rs:65:17
+ |
+LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:88:21
++ --> $DIR/expect_fun_call.rs:86:21
+ |
+LL | Some("foo").expect(&get_string());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:89:21
++ --> $DIR/expect_fun_call.rs:87:21
+ |
+LL | Some("foo").expect(get_string().as_ref());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:91:21
++ --> $DIR/expect_fun_call.rs:88:21
+ |
+LL | Some("foo").expect(get_string().as_str());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:92:21
++ --> $DIR/expect_fun_call.rs:90:21
+ |
+LL | Some("foo").expect(get_static_str());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:96:16
++ --> $DIR/expect_fun_call.rs:91:21
+ |
+LL | Some("foo").expect(get_non_static_str(&0));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
+
+error: use of `expect` followed by a function call
- --> $DIR/expect_fun_call.rs:102:17
++ --> $DIR/expect_fun_call.rs:95:16
+ |
+LL | Some(true).expect(&format!("key {}, {}", 1, 2));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
+
+error: use of `expect` followed by a function call
- error: aborting due to 13 previous errors
++ --> $DIR/expect_fun_call.rs:101:17
+ |
+LL | opt_ref.expect(&format!("{:?}", opt_ref));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))`
+
++error: use of `expect` followed by a function call
++ --> $DIR/expect_fun_call.rs:105:20
++ |
++LL | format_capture.expect(&format!("{error_code}"));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{error_code}"))`
++
++error: use of `expect` followed by a function call
++ --> $DIR/expect_fun_call.rs:108:30
++ |
++LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{error_code}, {}", 1))`
++
++error: aborting due to 15 previous errors
+
--- /dev/null
+#![warn(clippy::explicit_counter_loop)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ let mut vec = vec![1, 2, 3, 4];
+ let mut _index = 0;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 1;
+ _index = 0;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &mut vec {
+ _index += 1;
+ }
+
+ let mut _index = 0;
+ for _v in vec {
+ _index += 1;
+ }
+}
+
+mod issue_1219 {
+ pub fn test() {
+ // should not trigger the lint because variable is used after the loop #473
+ let vec = vec![1, 2, 3];
+ let mut index = 0;
+ for _v in &vec {
+ index += 1
+ }
+ println!("index: {}", index);
+
+ // should not trigger the lint because the count is conditional #1219
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ if ch == 'a' {
+ continue;
+ }
+ count += 1;
+ }
+
+ // should not trigger the lint because the count is conditional
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ if ch == 'a' {
+ count += 1;
+ }
+ }
+
+ // should trigger the lint because the count is not conditional
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ count += 1;
+ if ch == 'a' {
+ continue;
+ }
+ }
+
+ // should trigger the lint because the count is not conditional
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ count += 1;
+ for i in 0..2 {
+ let _ = 123;
+ }
+ }
+
+ // should not trigger the lint because the count is incremented multiple times
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ count += 1;
+ for i in 0..2 {
+ count += 1;
+ }
+ }
+ }
+}
+
+mod issue_3308 {
+ pub fn test() {
+ // should not trigger the lint because the count is incremented multiple times
+ let mut skips = 0;
+ let erasures = vec![];
+ for i in 0..10 {
+ println!("{}", skips);
+ while erasures.contains(&(i + skips)) {
+ skips += 1;
+ }
+ }
+
+ // should not trigger the lint because the count is incremented multiple times
+ let mut skips = 0;
+ for i in 0..10 {
+ println!("{}", skips);
+ let mut j = 0;
+ while j < 5 {
+ skips += 1;
+ j += 1;
+ }
+ }
+
+ // should not trigger the lint because the count is incremented multiple times
+ let mut skips = 0;
+ for i in 0..10 {
+ println!("{}", skips);
+ for j in 0..5 {
+ skips += 1;
+ }
+ }
+ }
+}
+
+mod issue_1670 {
+ pub fn test() {
+ let mut count = 0;
+ for _i in 3..10 {
+ count += 1;
+ }
+ }
+}
+
+mod issue_4732 {
+ pub fn test() {
+ let slice = &[1, 2, 3];
+ let mut index = 0;
+
+ // should not trigger the lint because the count is used after the loop
+ for _v in slice {
+ index += 1
+ }
+ let _closure = || println!("index: {}", index);
+ }
+}
+
+mod issue_4677 {
+ pub fn test() {
+ let slice = &[1, 2, 3];
+
+ // should not trigger the lint because the count is used after incremented
+ let mut count = 0;
+ for _i in slice {
+ count += 1;
+ println!("{}", count);
+ }
+ }
+}
+
+mod issue_7920 {
+ pub fn test() {
+ let slice = &[1, 2, 3];
+
+ let index_usize: usize = 0;
+ let mut idx_usize: usize = 0;
+
+ // should suggest `enumerate`
+ for _item in slice {
+ if idx_usize == index_usize {
+ break;
+ }
+
+ idx_usize += 1;
+ }
+
+ let index_u32: u32 = 0;
+ let mut idx_u32: u32 = 0;
+
+ // should suggest `zip`
+ for _item in slice {
+ if idx_u32 == index_u32 {
+ break;
+ }
+
+ idx_u32 += 1;
+ }
+ }
+}
--- /dev/null
- --> $DIR/explicit_counter_loop.rs:6:5
+error: the variable `_index` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:12:5
++ --> $DIR/explicit_counter_loop.rs:7:5
+ |
+LL | for _v in &vec {
+ | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
+ |
+ = note: `-D clippy::explicit-counter-loop` implied by `-D warnings`
+
+error: the variable `_index` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:17:5
++ --> $DIR/explicit_counter_loop.rs:13:5
+ |
+LL | for _v in &vec {
+ | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
+
+error: the variable `_index` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:22:5
++ --> $DIR/explicit_counter_loop.rs:18:5
+ |
+LL | for _v in &mut vec {
+ | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter_mut().enumerate()`
+
+error: the variable `_index` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:61:9
++ --> $DIR/explicit_counter_loop.rs:23:5
+ |
+LL | for _v in vec {
+ | ^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.into_iter().enumerate()`
+
+error: the variable `count` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:72:9
++ --> $DIR/explicit_counter_loop.rs:62:9
+ |
+LL | for ch in text.chars() {
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
+
+error: the variable `count` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:130:9
++ --> $DIR/explicit_counter_loop.rs:73:9
+ |
+LL | for ch in text.chars() {
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
+
+error: the variable `count` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:170:9
++ --> $DIR/explicit_counter_loop.rs:131:9
+ |
+LL | for _i in 3..10 {
+ | ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()`
+
+error: the variable `idx_usize` is used as a loop counter
- --> $DIR/explicit_counter_loop.rs:182:9
++ --> $DIR/explicit_counter_loop.rs:171:9
+ |
+LL | for _item in slice {
+ | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.iter().enumerate()`
+
+error: the variable `idx_u32` is used as a loop counter
++ --> $DIR/explicit_counter_loop.rs:183:9
+ |
+LL | for _item in slice {
+ | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.iter())`
+ |
+ = note: `idx_u32` is of type `u32`, making it ineligible for `Iterator::enumerate`
+
+error: aborting due to 9 previous errors
+
--- /dev/null
-
+// run-rustfix
- unused_variables,
++#![warn(clippy::explicit_deref_methods)]
++#![allow(unused_variables)]
+#![allow(
- clippy::borrow_deref_ref,
- clippy::explicit_auto_deref
++ clippy::borrow_deref_ref,
+ clippy::clone_double_ref,
++ clippy::explicit_auto_deref,
+ clippy::needless_borrow,
- #![warn(clippy::explicit_deref_methods)]
++ clippy::uninlined_format_args
+)]
+
+use std::ops::{Deref, DerefMut};
+
+fn concat(deref_str: &str) -> String {
+ format!("{}bar", deref_str)
+}
+
+fn just_return(deref_str: &str) -> &str {
+ deref_str
+}
+
+struct CustomVec(Vec<u8>);
+impl Deref for CustomVec {
+ type Target = Vec<u8>;
+
+ fn deref(&self) -> &Vec<u8> {
+ &self.0
+ }
+}
+
+fn main() {
+ let a: &mut String = &mut String::from("foo");
+
+ // these should require linting
+
+ let b: &str = &*a;
+
+ let b: &mut str = &mut **a;
+
+ // both derefs should get linted here
+ let b: String = format!("{}, {}", &*a, &*a);
+
+ println!("{}", &*a);
+
+ #[allow(clippy::match_single_binding)]
+ match &*a {
+ _ => (),
+ }
+
+ let b: String = concat(&*a);
+
+ let b = just_return(a);
+
+ let b: String = concat(just_return(a));
+
+ let b: &str = &**a;
+
+ let opt_a = Some(a.clone());
+ let b = &*opt_a.unwrap();
+
+ // following should not require linting
+
+ let cv = CustomVec(vec![0, 42]);
+ let c = cv.deref()[0];
+
+ let b: &str = &*a.deref();
+
+ let b: String = a.deref().clone();
+
+ let b: usize = a.deref_mut().len();
+
+ let b: &usize = &a.deref().len();
+
+ let b: &str = &*a;
+
+ let b: &mut str = &mut *a;
+
+ macro_rules! expr_deref {
+ ($body:expr) => {
+ $body.deref()
+ };
+ }
+ let b: &str = expr_deref!(a);
+
+ let b: &str = expr_deref!(&*a);
+
+ // The struct does not implement Deref trait
+ #[derive(Copy, Clone)]
+ struct NoLint(u32);
+ impl NoLint {
+ pub fn deref(self) -> u32 {
+ self.0
+ }
+ pub fn deref_mut(self) -> u32 {
+ self.0
+ }
+ }
+ let no_lint = NoLint(42);
+ let b = no_lint.deref();
+ let b = no_lint.deref_mut();
+}
--- /dev/null
-
+// run-rustfix
- unused_variables,
++#![warn(clippy::explicit_deref_methods)]
++#![allow(unused_variables)]
+#![allow(
- clippy::borrow_deref_ref,
- clippy::explicit_auto_deref
++ clippy::borrow_deref_ref,
+ clippy::clone_double_ref,
++ clippy::explicit_auto_deref,
+ clippy::needless_borrow,
- #![warn(clippy::explicit_deref_methods)]
++ clippy::uninlined_format_args
+)]
+
+use std::ops::{Deref, DerefMut};
+
+fn concat(deref_str: &str) -> String {
+ format!("{}bar", deref_str)
+}
+
+fn just_return(deref_str: &str) -> &str {
+ deref_str
+}
+
+struct CustomVec(Vec<u8>);
+impl Deref for CustomVec {
+ type Target = Vec<u8>;
+
+ fn deref(&self) -> &Vec<u8> {
+ &self.0
+ }
+}
+
+fn main() {
+ let a: &mut String = &mut String::from("foo");
+
+ // these should require linting
+
+ let b: &str = a.deref();
+
+ let b: &mut str = a.deref_mut();
+
+ // both derefs should get linted here
+ let b: String = format!("{}, {}", a.deref(), a.deref());
+
+ println!("{}", a.deref());
+
+ #[allow(clippy::match_single_binding)]
+ match a.deref() {
+ _ => (),
+ }
+
+ let b: String = concat(a.deref());
+
+ let b = just_return(a).deref();
+
+ let b: String = concat(just_return(a).deref());
+
+ let b: &str = a.deref().deref();
+
+ let opt_a = Some(a.clone());
+ let b = opt_a.unwrap().deref();
+
+ // following should not require linting
+
+ let cv = CustomVec(vec![0, 42]);
+ let c = cv.deref()[0];
+
+ let b: &str = &*a.deref();
+
+ let b: String = a.deref().clone();
+
+ let b: usize = a.deref_mut().len();
+
+ let b: &usize = &a.deref().len();
+
+ let b: &str = &*a;
+
+ let b: &mut str = &mut *a;
+
+ macro_rules! expr_deref {
+ ($body:expr) => {
+ $body.deref()
+ };
+ }
+ let b: &str = expr_deref!(a);
+
+ let b: &str = expr_deref!(a.deref());
+
+ // The struct does not implement Deref trait
+ #[derive(Copy, Clone)]
+ struct NoLint(u32);
+ impl NoLint {
+ pub fn deref(self) -> u32 {
+ self.0
+ }
+ pub fn deref_mut(self) -> u32 {
+ self.0
+ }
+ }
+ let no_lint = NoLint(42);
+ let b = no_lint.deref();
+ let b = no_lint.deref_mut();
+}
--- /dev/null
- #![allow(unused_imports)]
+// run-rustfix
+#![warn(clippy::explicit_write)]
++#![allow(unused_imports)]
++#![allow(clippy::uninlined_format_args)]
+
+fn stdout() -> String {
+ String::new()
+}
+
+fn stderr() -> String {
+ String::new()
+}
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ // these should warn
+ {
+ use std::io::Write;
+ print!("test");
+ eprint!("test");
+ println!("test");
+ eprintln!("test");
+ print!("test");
+ eprint!("test");
+
+ // including newlines
+ println!("test\ntest");
+ eprintln!("test\ntest");
+
+ let value = 1;
+ eprintln!("with {}", value);
+ eprintln!("with {} {}", 2, value);
+ eprintln!("with {value}");
+ eprintln!("macro arg {}", one!());
+ let width = 2;
+ eprintln!("{:w$}", value, w = width);
+ }
+ // these should not warn, different destination
+ {
+ use std::fmt::Write;
+ let mut s = String::new();
+ write!(s, "test").unwrap();
+ write!(s, "test").unwrap();
+ writeln!(s, "test").unwrap();
+ writeln!(s, "test").unwrap();
+ s.write_fmt(format_args!("test")).unwrap();
+ s.write_fmt(format_args!("test")).unwrap();
+ write!(stdout(), "test").unwrap();
+ write!(stderr(), "test").unwrap();
+ writeln!(stdout(), "test").unwrap();
+ writeln!(stderr(), "test").unwrap();
+ stdout().write_fmt(format_args!("test")).unwrap();
+ stderr().write_fmt(format_args!("test")).unwrap();
+ }
+ // these should not warn, no unwrap
+ {
+ use std::io::Write;
+ std::io::stdout().write_fmt(format_args!("test")).expect("no stdout");
+ std::io::stderr().write_fmt(format_args!("test")).expect("no stderr");
+ }
+}
--- /dev/null
- #![allow(unused_imports)]
+// run-rustfix
+#![warn(clippy::explicit_write)]
++#![allow(unused_imports)]
++#![allow(clippy::uninlined_format_args)]
+
+fn stdout() -> String {
+ String::new()
+}
+
+fn stderr() -> String {
+ String::new()
+}
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ // these should warn
+ {
+ use std::io::Write;
+ write!(std::io::stdout(), "test").unwrap();
+ write!(std::io::stderr(), "test").unwrap();
+ writeln!(std::io::stdout(), "test").unwrap();
+ writeln!(std::io::stderr(), "test").unwrap();
+ std::io::stdout().write_fmt(format_args!("test")).unwrap();
+ std::io::stderr().write_fmt(format_args!("test")).unwrap();
+
+ // including newlines
+ writeln!(std::io::stdout(), "test\ntest").unwrap();
+ writeln!(std::io::stderr(), "test\ntest").unwrap();
+
+ let value = 1;
+ writeln!(std::io::stderr(), "with {}", value).unwrap();
+ writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap();
+ writeln!(std::io::stderr(), "with {value}").unwrap();
+ writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap();
+ let width = 2;
+ writeln!(std::io::stderr(), "{:w$}", value, w = width).unwrap();
+ }
+ // these should not warn, different destination
+ {
+ use std::fmt::Write;
+ let mut s = String::new();
+ write!(s, "test").unwrap();
+ write!(s, "test").unwrap();
+ writeln!(s, "test").unwrap();
+ writeln!(s, "test").unwrap();
+ s.write_fmt(format_args!("test")).unwrap();
+ s.write_fmt(format_args!("test")).unwrap();
+ write!(stdout(), "test").unwrap();
+ write!(stderr(), "test").unwrap();
+ writeln!(stdout(), "test").unwrap();
+ writeln!(stderr(), "test").unwrap();
+ stdout().write_fmt(format_args!("test")).unwrap();
+ stderr().write_fmt(format_args!("test")).unwrap();
+ }
+ // these should not warn, no unwrap
+ {
+ use std::io::Write;
+ std::io::stdout().write_fmt(format_args!("test")).expect("no stdout");
+ std::io::stderr().write_fmt(format_args!("test")).expect("no stderr");
+ }
+}
--- /dev/null
- --> $DIR/explicit_write.rs:23:9
+error: use of `write!(stdout(), ...).unwrap()`
- --> $DIR/explicit_write.rs:24:9
++ --> $DIR/explicit_write.rs:24:9
+ |
+LL | write!(std::io::stdout(), "test").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")`
+ |
+ = note: `-D clippy::explicit-write` implied by `-D warnings`
+
+error: use of `write!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:25:9
++ --> $DIR/explicit_write.rs:25:9
+ |
+LL | write!(std::io::stderr(), "test").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")`
+
+error: use of `writeln!(stdout(), ...).unwrap()`
- --> $DIR/explicit_write.rs:26:9
++ --> $DIR/explicit_write.rs:26:9
+ |
+LL | writeln!(std::io::stdout(), "test").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test")`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:27:9
++ --> $DIR/explicit_write.rs:27:9
+ |
+LL | writeln!(std::io::stderr(), "test").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test")`
+
+error: use of `stdout().write_fmt(...).unwrap()`
- --> $DIR/explicit_write.rs:28:9
++ --> $DIR/explicit_write.rs:28:9
+ |
+LL | std::io::stdout().write_fmt(format_args!("test")).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")`
+
+error: use of `stderr().write_fmt(...).unwrap()`
- --> $DIR/explicit_write.rs:31:9
++ --> $DIR/explicit_write.rs:29:9
+ |
+LL | std::io::stderr().write_fmt(format_args!("test")).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")`
+
+error: use of `writeln!(stdout(), ...).unwrap()`
- --> $DIR/explicit_write.rs:32:9
++ --> $DIR/explicit_write.rs:32:9
+ |
+LL | writeln!(std::io::stdout(), "test/ntest").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test/ntest")`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:35:9
++ --> $DIR/explicit_write.rs:33:9
+ |
+LL | writeln!(std::io::stderr(), "test/ntest").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test/ntest")`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:36:9
++ --> $DIR/explicit_write.rs:36:9
+ |
+LL | writeln!(std::io::stderr(), "with {}", value).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("with {}", value)`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:37:9
++ --> $DIR/explicit_write.rs:37:9
+ |
+LL | writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("with {} {}", 2, value)`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:38:9
++ --> $DIR/explicit_write.rs:38:9
+ |
+LL | writeln!(std::io::stderr(), "with {value}").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("with {value}")`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
- --> $DIR/explicit_write.rs:40:9
++ --> $DIR/explicit_write.rs:39:9
+ |
+LL | writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("macro arg {}", one!())`
+
+error: use of `writeln!(stderr(), ...).unwrap()`
++ --> $DIR/explicit_write.rs:41:9
+ |
+LL | writeln!(std::io::stderr(), "{:w$}", value, w = width).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("{:w$}", value, w = width)`
+
+error: aborting due to 13 previous errors
+
--- /dev/null
+#![deny(clippy::fallible_impl_from)]
++#![allow(clippy::uninlined_format_args)]
+
+// docs example
+struct Foo(i32);
+impl From<String> for Foo {
+ fn from(s: String) -> Self {
+ Foo(s.parse().unwrap())
+ }
+}
+
+struct Valid(Vec<u8>);
+
+impl<'a> From<&'a str> for Valid {
+ fn from(s: &'a str) -> Valid {
+ Valid(s.to_owned().into_bytes())
+ }
+}
+impl From<usize> for Valid {
+ fn from(i: usize) -> Valid {
+ Valid(Vec::with_capacity(i))
+ }
+}
+
+struct Invalid;
+
+impl From<usize> for Invalid {
+ fn from(i: usize) -> Invalid {
+ if i != 42 {
+ panic!();
+ }
+ Invalid
+ }
+}
+
+impl From<Option<String>> for Invalid {
+ fn from(s: Option<String>) -> Invalid {
+ let s = s.unwrap();
+ if !s.is_empty() {
+ panic!("42");
+ } else if s.parse::<u32>().unwrap() != 42 {
+ panic!("{:?}", s);
+ }
+ Invalid
+ }
+}
+
+trait ProjStrTrait {
+ type ProjString;
+}
+impl<T> ProjStrTrait for Box<T> {
+ type ProjString = String;
+}
+impl<'a> From<&'a mut <Box<u32> as ProjStrTrait>::ProjString> for Invalid {
+ fn from(s: &'a mut <Box<u32> as ProjStrTrait>::ProjString) -> Invalid {
+ if s.parse::<u32>().ok().unwrap() != 42 {
+ panic!("{:?}", s);
+ }
+ Invalid
+ }
+}
+
+struct Unreachable;
+
+impl From<String> for Unreachable {
+ fn from(s: String) -> Unreachable {
+ if s.is_empty() {
+ return Unreachable;
+ }
+ match s.chars().next() {
+ Some(_) => Unreachable,
+ None => unreachable!(), // do not lint the unreachable macro
+ }
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/fallible_impl_from.rs:5:1
+error: consider implementing `TryFrom` instead
- --> $DIR/fallible_impl_from.rs:7:13
++ --> $DIR/fallible_impl_from.rs:6:1
+ |
+LL | / impl From<String> for Foo {
+LL | | fn from(s: String) -> Self {
+LL | | Foo(s.parse().unwrap())
+LL | | }
+LL | | }
+ | |_^
+ |
+ = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail
+note: potential failure(s)
- --> $DIR/fallible_impl_from.rs:26:1
++ --> $DIR/fallible_impl_from.rs:8:13
+ |
+LL | Foo(s.parse().unwrap())
+ | ^^^^^^^^^^^^^^^^^^
+note: the lint level is defined here
+ --> $DIR/fallible_impl_from.rs:1:9
+ |
+LL | #![deny(clippy::fallible_impl_from)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: consider implementing `TryFrom` instead
- --> $DIR/fallible_impl_from.rs:29:13
++ --> $DIR/fallible_impl_from.rs:27:1
+ |
+LL | / impl From<usize> for Invalid {
+LL | | fn from(i: usize) -> Invalid {
+LL | | if i != 42 {
+LL | | panic!();
+... |
+LL | | }
+LL | | }
+ | |_^
+ |
+ = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail
+note: potential failure(s)
- --> $DIR/fallible_impl_from.rs:35:1
++ --> $DIR/fallible_impl_from.rs:30:13
+ |
+LL | panic!();
+ | ^^^^^^^^
+ = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: consider implementing `TryFrom` instead
- --> $DIR/fallible_impl_from.rs:37:17
++ --> $DIR/fallible_impl_from.rs:36:1
+ |
+LL | / impl From<Option<String>> for Invalid {
+LL | | fn from(s: Option<String>) -> Invalid {
+LL | | let s = s.unwrap();
+LL | | if !s.is_empty() {
+... |
+LL | | }
+LL | | }
+ | |_^
+ |
+ = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail
+note: potential failure(s)
- --> $DIR/fallible_impl_from.rs:53:1
++ --> $DIR/fallible_impl_from.rs:38:17
+ |
+LL | let s = s.unwrap();
+ | ^^^^^^^^^^
+LL | if !s.is_empty() {
+LL | panic!("42");
+ | ^^^^^^^^^^^^
+LL | } else if s.parse::<u32>().unwrap() != 42 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | panic!("{:?}", s);
+ | ^^^^^^^^^^^^^^^^^
+ = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: consider implementing `TryFrom` instead
- --> $DIR/fallible_impl_from.rs:55:12
++ --> $DIR/fallible_impl_from.rs:54:1
+ |
+LL | / impl<'a> From<&'a mut <Box<u32> as ProjStrTrait>::ProjString> for Invalid {
+LL | | fn from(s: &'a mut <Box<u32> as ProjStrTrait>::ProjString) -> Invalid {
+LL | | if s.parse::<u32>().ok().unwrap() != 42 {
+LL | | panic!("{:?}", s);
+... |
+LL | | }
+LL | | }
+ | |_^
+ |
+ = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail
+note: potential failure(s)
++ --> $DIR/fallible_impl_from.rs:56:12
+ |
+LL | if s.parse::<u32>().ok().unwrap() != 42 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | panic!("{:?}", s);
+ | ^^^^^^^^^^^^^^^^^
+ = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 4 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::imprecise_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let x = 2f32;
+ let _ = x.exp_m1();
+ let _ = x.exp_m1() + 2.0;
+ let _ = (x as f32).exp_m1() + 2.0;
+ // Cases where the lint shouldn't be applied
+ let _ = x.exp() - 2.0;
+ let _ = x.exp() - 1.0 * 2.0;
+
+ let x = 2f64;
+ let _ = x.exp_m1();
+ let _ = x.exp_m1() + 2.0;
+ // Cases where the lint shouldn't be applied
+ let _ = x.exp() - 2.0;
+ let _ = x.exp() - 1.0 * 2.0;
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::imprecise_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let x = 2f32;
+ let _ = x.exp() - 1.0;
+ let _ = x.exp() - 1.0 + 2.0;
+ let _ = (x as f32).exp() - 1.0 + 2.0;
+ // Cases where the lint shouldn't be applied
+ let _ = x.exp() - 2.0;
+ let _ = x.exp() - 1.0 * 2.0;
+
+ let x = 2f64;
+ let _ = x.exp() - 1.0;
+ let _ = x.exp() - 1.0 + 2.0;
+ // Cases where the lint shouldn't be applied
+ let _ = x.exp() - 2.0;
+ let _ = x.exp() - 1.0 * 2.0;
+}
--- /dev/null
- --> $DIR/floating_point_exp.rs:6:13
+error: (e.pow(x) - 1) can be computed more accurately
- --> $DIR/floating_point_exp.rs:7:13
++ --> $DIR/floating_point_exp.rs:7:13
+ |
+LL | let _ = x.exp() - 1.0;
+ | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()`
+ |
+ = note: `-D clippy::imprecise-flops` implied by `-D warnings`
+
+error: (e.pow(x) - 1) can be computed more accurately
- --> $DIR/floating_point_exp.rs:8:13
++ --> $DIR/floating_point_exp.rs:8:13
+ |
+LL | let _ = x.exp() - 1.0 + 2.0;
+ | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()`
+
+error: (e.pow(x) - 1) can be computed more accurately
- --> $DIR/floating_point_exp.rs:14:13
++ --> $DIR/floating_point_exp.rs:9:13
+ |
+LL | let _ = (x as f32).exp() - 1.0 + 2.0;
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).exp_m1()`
+
+error: (e.pow(x) - 1) can be computed more accurately
- --> $DIR/floating_point_exp.rs:15:13
++ --> $DIR/floating_point_exp.rs:15:13
+ |
+LL | let _ = x.exp() - 1.0;
+ | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()`
+
+error: (e.pow(x) - 1) can be computed more accurately
++ --> $DIR/floating_point_exp.rs:16:13
+ |
+LL | let _ = x.exp() - 1.0 + 2.0;
+ | ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()`
+
+error: aborting due to 5 previous errors
+
--- /dev/null
- #![allow(dead_code, clippy::double_parens)]
+// run-rustfix
++#![allow(dead_code, clippy::double_parens, clippy::unnecessary_cast)]
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
+
+const TWO: f32 = 2.0;
+const E: f32 = std::f32::consts::E;
+
+fn check_log_base() {
+ let x = 1f32;
+ let _ = x.log2();
+ let _ = x.log10();
+ let _ = x.ln();
+ let _ = x.log2();
+ let _ = x.ln();
+ let _ = (x as f32).log2();
+
+ let x = 1f64;
+ let _ = x.log2();
+ let _ = x.log10();
+ let _ = x.ln();
+}
+
+fn check_ln1p() {
+ let x = 1f32;
+ let _ = 2.0f32.ln_1p();
+ let _ = 2.0f32.ln_1p();
+ let _ = x.ln_1p();
+ let _ = (x / 2.0).ln_1p();
+ let _ = x.powi(3).ln_1p();
+ let _ = (x.powi(3) / 2.0).ln_1p();
+ let _ = (std::f32::consts::E - 1.0).ln_1p();
+ let _ = x.ln_1p();
+ let _ = x.powi(3).ln_1p();
+ let _ = (x + 2.0).ln_1p();
+ let _ = (x / 2.0).ln_1p();
+ // Cases where the lint shouldn't be applied
+ let _ = (1.0 + x + 2.0).ln();
+ let _ = (x + 1.0 + 2.0).ln();
+ let _ = (x + 1.0 / 2.0).ln();
+ let _ = (1.0 + x - 2.0).ln();
+
+ let x = 1f64;
+ let _ = 2.0f64.ln_1p();
+ let _ = 2.0f64.ln_1p();
+ let _ = x.ln_1p();
+ let _ = (x / 2.0).ln_1p();
+ let _ = x.powi(3).ln_1p();
+ let _ = x.ln_1p();
+ let _ = x.powi(3).ln_1p();
+ let _ = (x + 2.0).ln_1p();
+ let _ = (x / 2.0).ln_1p();
+ // Cases where the lint shouldn't be applied
+ let _ = (1.0 + x + 2.0).ln();
+ let _ = (x + 1.0 + 2.0).ln();
+ let _ = (x + 1.0 / 2.0).ln();
+ let _ = (1.0 + x - 2.0).ln();
+}
+
+fn main() {}
--- /dev/null
- #![allow(dead_code, clippy::double_parens)]
+// run-rustfix
++#![allow(dead_code, clippy::double_parens, clippy::unnecessary_cast)]
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
+
+const TWO: f32 = 2.0;
+const E: f32 = std::f32::consts::E;
+
+fn check_log_base() {
+ let x = 1f32;
+ let _ = x.log(2f32);
+ let _ = x.log(10f32);
+ let _ = x.log(std::f32::consts::E);
+ let _ = x.log(TWO);
+ let _ = x.log(E);
+ let _ = (x as f32).log(2f32);
+
+ let x = 1f64;
+ let _ = x.log(2f64);
+ let _ = x.log(10f64);
+ let _ = x.log(std::f64::consts::E);
+}
+
+fn check_ln1p() {
+ let x = 1f32;
+ let _ = (1f32 + 2.).ln();
+ let _ = (1f32 + 2.0).ln();
+ let _ = (1.0 + x).ln();
+ let _ = (1.0 + x / 2.0).ln();
+ let _ = (1.0 + x.powi(3)).ln();
+ let _ = (1.0 + x.powi(3) / 2.0).ln();
+ let _ = (1.0 + (std::f32::consts::E - 1.0)).ln();
+ let _ = (x + 1.0).ln();
+ let _ = (x.powi(3) + 1.0).ln();
+ let _ = (x + 2.0 + 1.0).ln();
+ let _ = (x / 2.0 + 1.0).ln();
+ // Cases where the lint shouldn't be applied
+ let _ = (1.0 + x + 2.0).ln();
+ let _ = (x + 1.0 + 2.0).ln();
+ let _ = (x + 1.0 / 2.0).ln();
+ let _ = (1.0 + x - 2.0).ln();
+
+ let x = 1f64;
+ let _ = (1f64 + 2.).ln();
+ let _ = (1f64 + 2.0).ln();
+ let _ = (1.0 + x).ln();
+ let _ = (1.0 + x / 2.0).ln();
+ let _ = (1.0 + x.powi(3)).ln();
+ let _ = (x + 1.0).ln();
+ let _ = (x.powi(3) + 1.0).ln();
+ let _ = (x + 2.0 + 1.0).ln();
+ let _ = (x / 2.0 + 1.0).ln();
+ // Cases where the lint shouldn't be applied
+ let _ = (1.0 + x + 2.0).ln();
+ let _ = (x + 1.0 + 2.0).ln();
+ let _ = (x + 1.0 / 2.0).ln();
+ let _ = (1.0 + x - 2.0).ln();
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+#![warn(clippy::suboptimal_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let x = 3f32;
+ let y = 5f32;
+ let _ = x.log(y);
+ let _ = (x as f32).log(y);
+ let _ = x.log(y);
+ let _ = x.log(y);
+ let _ = x.log(y);
+ // Cases where the lint shouldn't be applied
+ let _ = x.ln() / y.powf(3.2);
+ let _ = x.powf(3.2) / y.powf(3.2);
+ let _ = x.powf(3.2) / y.ln();
+ let _ = x.log(5f32) / y.log(7f32);
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::suboptimal_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let x = 3f32;
+ let y = 5f32;
+ let _ = x.ln() / y.ln();
+ let _ = (x as f32).ln() / y.ln();
+ let _ = x.log2() / y.log2();
+ let _ = x.log10() / y.log10();
+ let _ = x.log(5f32) / y.log(5f32);
+ // Cases where the lint shouldn't be applied
+ let _ = x.ln() / y.powf(3.2);
+ let _ = x.powf(3.2) / y.powf(3.2);
+ let _ = x.powf(3.2) / y.ln();
+ let _ = x.log(5f32) / y.log(7f32);
+}
--- /dev/null
- --> $DIR/floating_point_logbase.rs:7:13
+error: log base can be expressed more clearly
- --> $DIR/floating_point_logbase.rs:8:13
++ --> $DIR/floating_point_logbase.rs:8:13
+ |
+LL | let _ = x.ln() / y.ln();
+ | ^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: log base can be expressed more clearly
- --> $DIR/floating_point_logbase.rs:9:13
++ --> $DIR/floating_point_logbase.rs:9:13
+ |
+LL | let _ = (x as f32).ln() / y.ln();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).log(y)`
+
+error: log base can be expressed more clearly
- --> $DIR/floating_point_logbase.rs:10:13
++ --> $DIR/floating_point_logbase.rs:10:13
+ |
+LL | let _ = x.log2() / y.log2();
+ | ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
+
+error: log base can be expressed more clearly
- --> $DIR/floating_point_logbase.rs:11:13
++ --> $DIR/floating_point_logbase.rs:11:13
+ |
+LL | let _ = x.log10() / y.log10();
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
+
+error: log base can be expressed more clearly
++ --> $DIR/floating_point_logbase.rs:12:13
+ |
+LL | let _ = x.log(5f32) / y.log(5f32);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
+
+error: aborting due to 5 previous errors
+
--- /dev/null
+// run-rustfix
+#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
+/// Allow suboptimal_ops in constant context
+pub const fn in_const_context() {
+ let a: f64 = 1234.567;
+ let b: f64 = 45.67834;
+ let c: f64 = 0.0004;
+
+ let _ = a * b + c;
+ let _ = c + a * b;
+}
+
+fn main() {
+ let a: f64 = 1234.567;
+ let b: f64 = 45.67834;
+ let c: f64 = 0.0004;
+ let d: f64 = 0.0001;
+
+ let _ = a.mul_add(b, c);
++ let _ = a.mul_add(b, -c);
+ let _ = a.mul_add(b, c);
++ let _ = a.mul_add(-b, c);
+ let _ = 2.0f64.mul_add(4.0, a);
+ let _ = 2.0f64.mul_add(4., a);
+
+ let _ = a.mul_add(b, c);
+ let _ = a.mul_add(b, c);
+ let _ = (a * b).mul_add(c, d);
+
+ let _ = a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c)) + c;
+ let _ = 1234.567_f64.mul_add(45.67834_f64, 0.0004_f64);
+
+ let _ = a.mul_add(a, b).sqrt();
+
+ // Cases where the lint shouldn't be applied
+ let _ = (a * a + b * b).sqrt();
+}
--- /dev/null
+// run-rustfix
+#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
+/// Allow suboptimal_ops in constant context
+pub const fn in_const_context() {
+ let a: f64 = 1234.567;
+ let b: f64 = 45.67834;
+ let c: f64 = 0.0004;
+
+ let _ = a * b + c;
+ let _ = c + a * b;
+}
+
+fn main() {
+ let a: f64 = 1234.567;
+ let b: f64 = 45.67834;
+ let c: f64 = 0.0004;
+ let d: f64 = 0.0001;
+
+ let _ = a * b + c;
++ let _ = a * b - c;
+ let _ = c + a * b;
++ let _ = c - a * b;
+ let _ = a + 2.0 * 4.0;
+ let _ = a + 2. * 4.;
+
+ let _ = (a * b) + c;
+ let _ = c + (a * b);
+ let _ = a * b * c + d;
+
+ let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c;
+ let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64;
+
+ let _ = (a * a + b).sqrt();
+
+ // Cases where the lint shouldn't be applied
+ let _ = (a * a + b * b).sqrt();
+}
--- /dev/null
- --> $DIR/floating_point_mul_add.rs:23:13
+error: multiply and add expressions can be calculated more efficiently and accurately
+ --> $DIR/floating_point_mul_add.rs:21:13
+ |
+LL | let _ = a * b + c;
+ | ^^^^^^^^^ help: consider using: `a.mul_add(b, c)`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
+ --> $DIR/floating_point_mul_add.rs:22:13
+ |
++LL | let _ = a * b - c;
++ | ^^^^^^^^^ help: consider using: `a.mul_add(b, -c)`
++
++error: multiply and add expressions can be calculated more efficiently and accurately
++ --> $DIR/floating_point_mul_add.rs:23:13
++ |
+LL | let _ = c + a * b;
+ | ^^^^^^^^^ help: consider using: `a.mul_add(b, c)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:24:13
++ --> $DIR/floating_point_mul_add.rs:24:13
++ |
++LL | let _ = c - a * b;
++ | ^^^^^^^^^ help: consider using: `a.mul_add(-b, c)`
++
++error: multiply and add expressions can be calculated more efficiently and accurately
++ --> $DIR/floating_point_mul_add.rs:25:13
+ |
+LL | let _ = a + 2.0 * 4.0;
+ | ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4.0, a)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:26:13
++ --> $DIR/floating_point_mul_add.rs:26:13
+ |
+LL | let _ = a + 2. * 4.;
+ | ^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4., a)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:27:13
++ --> $DIR/floating_point_mul_add.rs:28:13
+ |
+LL | let _ = (a * b) + c;
+ | ^^^^^^^^^^^ help: consider using: `a.mul_add(b, c)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:28:13
++ --> $DIR/floating_point_mul_add.rs:29:13
+ |
+LL | let _ = c + (a * b);
+ | ^^^^^^^^^^^ help: consider using: `a.mul_add(b, c)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:30:13
++ --> $DIR/floating_point_mul_add.rs:30:13
+ |
+LL | let _ = a * b * c + d;
+ | ^^^^^^^^^^^^^ help: consider using: `(a * b).mul_add(c, d)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:31:13
++ --> $DIR/floating_point_mul_add.rs:32:13
+ |
+LL | let _ = a.mul_add(b, c) * a.mul_add(b, c) + a.mul_add(b, c) + c;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `a.mul_add(b, c).mul_add(a.mul_add(b, c), a.mul_add(b, c))`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:33:13
++ --> $DIR/floating_point_mul_add.rs:33:13
+ |
+LL | let _ = 1234.567_f64 * 45.67834_f64 + 0.0004_f64;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1234.567_f64.mul_add(45.67834_f64, 0.0004_f64)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- error: aborting due to 10 previous errors
++ --> $DIR/floating_point_mul_add.rs:35:13
+ |
+LL | let _ = (a * a + b).sqrt();
+ | ^^^^^^^^^^^ help: consider using: `a.mul_add(a, b)`
+
++error: aborting due to 12 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let x = 3f32;
+ let _ = x.exp2();
+ let _ = 3.1f32.exp2();
+ let _ = (-3.1f32).exp2();
+ let _ = x.exp();
+ let _ = 3.1f32.exp();
+ let _ = (-3.1f32).exp();
+ let _ = x.sqrt();
+ let _ = x.cbrt();
+ let _ = (x as f32).cbrt();
+ let _ = x.powi(3);
+ let _ = x.powi(-2);
+ let _ = x.powi(16_777_215);
+ let _ = x.powi(-16_777_215);
+ let _ = (x as f32).powi(-16_777_215);
+ let _ = (x as f32).powi(3);
+ let _ = (1.5_f32 + 1.0).cbrt();
+ let _ = 1.5_f64.cbrt();
+ let _ = 1.5_f64.sqrt();
+ let _ = 1.5_f64.powi(3);
+
+ // Cases where the lint shouldn't be applied
+ let _ = x.powf(2.1);
+ let _ = x.powf(-2.1);
+ let _ = x.powf(16_777_216.0);
+ let _ = x.powf(-16_777_216.0);
+
+ let x = 3f64;
+ let _ = x.exp2();
+ let _ = 3.1f64.exp2();
+ let _ = (-3.1f64).exp2();
+ let _ = x.exp();
+ let _ = 3.1f64.exp();
+ let _ = (-3.1f64).exp();
+ let _ = x.sqrt();
+ let _ = x.cbrt();
+ let _ = x.powi(3);
+ let _ = x.powi(-2);
+ let _ = x.powi(-2_147_483_648);
+ let _ = x.powi(2_147_483_647);
+ // Cases where the lint shouldn't be applied
+ let _ = x.powf(2.1);
+ let _ = x.powf(-2.1);
+ let _ = x.powf(-2_147_483_649.0);
+ let _ = x.powf(2_147_483_648.0);
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::suboptimal_flops, clippy::imprecise_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let x = 3f32;
+ let _ = 2f32.powf(x);
+ let _ = 2f32.powf(3.1);
+ let _ = 2f32.powf(-3.1);
+ let _ = std::f32::consts::E.powf(x);
+ let _ = std::f32::consts::E.powf(3.1);
+ let _ = std::f32::consts::E.powf(-3.1);
+ let _ = x.powf(1.0 / 2.0);
+ let _ = x.powf(1.0 / 3.0);
+ let _ = (x as f32).powf(1.0 / 3.0);
+ let _ = x.powf(3.0);
+ let _ = x.powf(-2.0);
+ let _ = x.powf(16_777_215.0);
+ let _ = x.powf(-16_777_215.0);
+ let _ = (x as f32).powf(-16_777_215.0);
+ let _ = (x as f32).powf(3.0);
+ let _ = (1.5_f32 + 1.0).powf(1.0 / 3.0);
+ let _ = 1.5_f64.powf(1.0 / 3.0);
+ let _ = 1.5_f64.powf(1.0 / 2.0);
+ let _ = 1.5_f64.powf(3.0);
+
+ // Cases where the lint shouldn't be applied
+ let _ = x.powf(2.1);
+ let _ = x.powf(-2.1);
+ let _ = x.powf(16_777_216.0);
+ let _ = x.powf(-16_777_216.0);
+
+ let x = 3f64;
+ let _ = 2f64.powf(x);
+ let _ = 2f64.powf(3.1);
+ let _ = 2f64.powf(-3.1);
+ let _ = std::f64::consts::E.powf(x);
+ let _ = std::f64::consts::E.powf(3.1);
+ let _ = std::f64::consts::E.powf(-3.1);
+ let _ = x.powf(1.0 / 2.0);
+ let _ = x.powf(1.0 / 3.0);
+ let _ = x.powf(3.0);
+ let _ = x.powf(-2.0);
+ let _ = x.powf(-2_147_483_648.0);
+ let _ = x.powf(2_147_483_647.0);
+ // Cases where the lint shouldn't be applied
+ let _ = x.powf(2.1);
+ let _ = x.powf(-2.1);
+ let _ = x.powf(-2_147_483_649.0);
+ let _ = x.powf(2_147_483_648.0);
+}
--- /dev/null
- --> $DIR/floating_point_powf.rs:6:13
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:7:13
++ --> $DIR/floating_point_powf.rs:7:13
+ |
+LL | let _ = 2f32.powf(x);
+ | ^^^^^^^^^^^^ help: consider using: `x.exp2()`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:8:13
++ --> $DIR/floating_point_powf.rs:8:13
+ |
+LL | let _ = 2f32.powf(3.1);
+ | ^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:9:13
++ --> $DIR/floating_point_powf.rs:9:13
+ |
+LL | let _ = 2f32.powf(-3.1);
+ | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:10:13
++ --> $DIR/floating_point_powf.rs:10:13
+ |
+LL | let _ = std::f32::consts::E.powf(x);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:11:13
++ --> $DIR/floating_point_powf.rs:11:13
+ |
+LL | let _ = std::f32::consts::E.powf(3.1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f32.exp()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:12:13
++ --> $DIR/floating_point_powf.rs:12:13
+ |
+LL | let _ = std::f32::consts::E.powf(-3.1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f32).exp()`
+
+error: square-root of a number can be computed more efficiently and accurately
- --> $DIR/floating_point_powf.rs:13:13
++ --> $DIR/floating_point_powf.rs:13:13
+ |
+LL | let _ = x.powf(1.0 / 2.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()`
+
+error: cube-root of a number can be computed more accurately
- --> $DIR/floating_point_powf.rs:14:13
++ --> $DIR/floating_point_powf.rs:14:13
+ |
+LL | let _ = x.powf(1.0 / 3.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()`
+ |
+ = note: `-D clippy::imprecise-flops` implied by `-D warnings`
+
+error: cube-root of a number can be computed more accurately
- --> $DIR/floating_point_powf.rs:15:13
++ --> $DIR/floating_point_powf.rs:15:13
+ |
+LL | let _ = (x as f32).powf(1.0 / 3.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).cbrt()`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:16:13
++ --> $DIR/floating_point_powf.rs:16:13
+ |
+LL | let _ = x.powf(3.0);
+ | ^^^^^^^^^^^ help: consider using: `x.powi(3)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:17:13
++ --> $DIR/floating_point_powf.rs:17:13
+ |
+LL | let _ = x.powf(-2.0);
+ | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:18:13
++ --> $DIR/floating_point_powf.rs:18:13
+ |
+LL | let _ = x.powf(16_777_215.0);
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(16_777_215)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:19:13
++ --> $DIR/floating_point_powf.rs:19:13
+ |
+LL | let _ = x.powf(-16_777_215.0);
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-16_777_215)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:20:13
++ --> $DIR/floating_point_powf.rs:20:13
+ |
+LL | let _ = (x as f32).powf(-16_777_215.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).powi(-16_777_215)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:21:13
++ --> $DIR/floating_point_powf.rs:21:13
+ |
+LL | let _ = (x as f32).powf(3.0);
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).powi(3)`
+
+error: cube-root of a number can be computed more accurately
- --> $DIR/floating_point_powf.rs:22:13
++ --> $DIR/floating_point_powf.rs:22:13
+ |
+LL | let _ = (1.5_f32 + 1.0).powf(1.0 / 3.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(1.5_f32 + 1.0).cbrt()`
+
+error: cube-root of a number can be computed more accurately
- --> $DIR/floating_point_powf.rs:23:13
++ --> $DIR/floating_point_powf.rs:23:13
+ |
+LL | let _ = 1.5_f64.powf(1.0 / 3.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1.5_f64.cbrt()`
+
+error: square-root of a number can be computed more efficiently and accurately
- --> $DIR/floating_point_powf.rs:24:13
++ --> $DIR/floating_point_powf.rs:24:13
+ |
+LL | let _ = 1.5_f64.powf(1.0 / 2.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `1.5_f64.sqrt()`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:33:13
++ --> $DIR/floating_point_powf.rs:25:13
+ |
+LL | let _ = 1.5_f64.powf(3.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `1.5_f64.powi(3)`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:34:13
++ --> $DIR/floating_point_powf.rs:34:13
+ |
+LL | let _ = 2f64.powf(x);
+ | ^^^^^^^^^^^^ help: consider using: `x.exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:35:13
++ --> $DIR/floating_point_powf.rs:35:13
+ |
+LL | let _ = 2f64.powf(3.1);
+ | ^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:36:13
++ --> $DIR/floating_point_powf.rs:36:13
+ |
+LL | let _ = 2f64.powf(-3.1);
+ | ^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp2()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:37:13
++ --> $DIR/floating_point_powf.rs:37:13
+ |
+LL | let _ = std::f64::consts::E.powf(x);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.exp()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:38:13
++ --> $DIR/floating_point_powf.rs:38:13
+ |
+LL | let _ = std::f64::consts::E.powf(3.1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `3.1f64.exp()`
+
+error: exponent for bases 2 and e can be computed more accurately
- --> $DIR/floating_point_powf.rs:39:13
++ --> $DIR/floating_point_powf.rs:39:13
+ |
+LL | let _ = std::f64::consts::E.powf(-3.1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-3.1f64).exp()`
+
+error: square-root of a number can be computed more efficiently and accurately
- --> $DIR/floating_point_powf.rs:40:13
++ --> $DIR/floating_point_powf.rs:40:13
+ |
+LL | let _ = x.powf(1.0 / 2.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `x.sqrt()`
+
+error: cube-root of a number can be computed more accurately
- --> $DIR/floating_point_powf.rs:41:13
++ --> $DIR/floating_point_powf.rs:41:13
+ |
+LL | let _ = x.powf(1.0 / 3.0);
+ | ^^^^^^^^^^^^^^^^^ help: consider using: `x.cbrt()`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:42:13
++ --> $DIR/floating_point_powf.rs:42:13
+ |
+LL | let _ = x.powf(3.0);
+ | ^^^^^^^^^^^ help: consider using: `x.powi(3)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:43:13
++ --> $DIR/floating_point_powf.rs:43:13
+ |
+LL | let _ = x.powf(-2.0);
+ | ^^^^^^^^^^^^ help: consider using: `x.powi(-2)`
+
+error: exponentiation with integer powers can be computed more efficiently
- --> $DIR/floating_point_powf.rs:44:13
++ --> $DIR/floating_point_powf.rs:44:13
+ |
+LL | let _ = x.powf(-2_147_483_648.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(-2_147_483_648)`
+
+error: exponentiation with integer powers can be computed more efficiently
++ --> $DIR/floating_point_powf.rs:45:13
+ |
+LL | let _ = x.powf(2_147_483_647.0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(2_147_483_647)`
+
+error: aborting due to 31 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::suboptimal_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let one = 1;
+ let x = 3f32;
+
+ let y = 4f32;
+ let _ = x.mul_add(x, y);
++ let _ = x.mul_add(x, -y);
+ let _ = y.mul_add(y, x);
++ let _ = y.mul_add(-y, x);
+ let _ = (y as f32).mul_add(y as f32, x);
+ let _ = x.mul_add(x, y).sqrt();
+ let _ = y.mul_add(y, x).sqrt();
+ // Cases where the lint shouldn't be applied
+ let _ = x.powi(2);
+ let _ = x.powi(1 + 1);
+ let _ = x.powi(3);
+ let _ = x.powi(4) + y;
+ let _ = x.powi(one + 1);
+ let _ = (x.powi(2) + y.powi(2)).sqrt();
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::suboptimal_flops)]
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let one = 1;
+ let x = 3f32;
+
+ let y = 4f32;
+ let _ = x.powi(2) + y;
++ let _ = x.powi(2) - y;
+ let _ = x + y.powi(2);
++ let _ = x - y.powi(2);
+ let _ = x + (y as f32).powi(2);
+ let _ = (x.powi(2) + y).sqrt();
+ let _ = (x + y.powi(2)).sqrt();
+ // Cases where the lint shouldn't be applied
+ let _ = x.powi(2);
+ let _ = x.powi(1 + 1);
+ let _ = x.powi(3);
+ let _ = x.powi(4) + y;
+ let _ = x.powi(one + 1);
+ let _ = (x.powi(2) + y.powi(2)).sqrt();
+}
--- /dev/null
- --> $DIR/floating_point_powi.rs:9:13
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_powi.rs:10:13
++ --> $DIR/floating_point_powi.rs:10:13
+ |
+LL | let _ = x.powi(2) + y;
+ | ^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_powi.rs:11:13
++ --> $DIR/floating_point_powi.rs:11:13
++ |
++LL | let _ = x.powi(2) - y;
++ | ^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, -y)`
++
++error: multiply and add expressions can be calculated more efficiently and accurately
++ --> $DIR/floating_point_powi.rs:12:13
+ |
+LL | let _ = x + y.powi(2);
+ | ^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_powi.rs:12:13
++ --> $DIR/floating_point_powi.rs:13:13
++ |
++LL | let _ = x - y.powi(2);
++ | ^^^^^^^^^^^^^ help: consider using: `y.mul_add(-y, x)`
++
++error: multiply and add expressions can be calculated more efficiently and accurately
++ --> $DIR/floating_point_powi.rs:14:13
+ |
+LL | let _ = x + (y as f32).powi(2);
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(y as f32).mul_add(y as f32, x)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_powi.rs:13:13
++ --> $DIR/floating_point_powi.rs:15:13
+ |
+LL | let _ = (x.powi(2) + y).sqrt();
+ | ^^^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)`
+
+error: multiply and add expressions can be calculated more efficiently and accurately
- error: aborting due to 5 previous errors
++ --> $DIR/floating_point_powi.rs:16:13
+ |
+LL | let _ = (x + y.powi(2)).sqrt();
+ | ^^^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)`
+
++error: aborting due to 7 previous errors
+
--- /dev/null
-
+// run-rustfix
+#![allow(dead_code, unused)]
++#![allow(clippy::uninlined_format_args)]
+
+use std::collections::*;
+
+#[warn(clippy::all)]
+struct Unrelated(Vec<u8>);
+impl Unrelated {
+ fn next(&self) -> std::slice::Iter<u8> {
+ self.0.iter()
+ }
+
+ fn iter(&self) -> std::slice::Iter<u8> {
+ self.0.iter()
+ }
+}
+
+#[warn(
+ clippy::needless_range_loop,
+ clippy::explicit_iter_loop,
+ clippy::explicit_into_iter_loop,
+ clippy::iter_next_loop,
+ clippy::for_kv_map
+)]
+#[allow(
+ clippy::linkedlist,
+ clippy::unnecessary_mut_passed,
+ clippy::similar_names,
+ clippy::needless_borrow
+)]
+#[allow(unused_variables)]
+fn main() {
+ let mut vec = vec![1, 2, 3, 4];
+
+ // See #601
+ for i in 0..10 {
+ // no error, id_col does not exist outside the loop
+ let mut id_col = vec![0f64; 10];
+ id_col[i] = 1f64;
+ }
+
+ for _v in &vec {}
+
+ for _v in &mut vec {}
+
+ let out_vec = vec![1, 2, 3];
+ for _v in out_vec {}
+
+ for _v in &vec {} // these are fine
+ for _v in &mut vec {} // these are fine
+
+ for _v in &[1, 2, 3] {}
+
+ for _v in (&mut [1, 2, 3]).iter() {} // no error
+
+ for _v in &[0; 32] {}
+
+ for _v in [0; 33].iter() {} // no error
+
+ let ll: LinkedList<()> = LinkedList::new();
+ for _v in &ll {}
+
+ let vd: VecDeque<()> = VecDeque::new();
+ for _v in &vd {}
+
+ let bh: BinaryHeap<()> = BinaryHeap::new();
+ for _v in &bh {}
+
+ let hm: HashMap<(), ()> = HashMap::new();
+ for _v in &hm {}
+
+ let bt: BTreeMap<(), ()> = BTreeMap::new();
+ for _v in &bt {}
+
+ let hs: HashSet<()> = HashSet::new();
+ for _v in &hs {}
+
+ let bs: BTreeSet<()> = BTreeSet::new();
+ for _v in &bs {}
+
+ let u = Unrelated(vec![]);
+ for _v in u.next() {} // no error
+ for _v in u.iter() {} // no error
+
+ let mut out = vec![];
+ vec.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>();
+ let _y = vec.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>(); // this is fine
+
+ // Loop with explicit counter variable
+
+ // Potential false positives
+ let mut _index = 0;
+ _index = 1;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ _index += 1;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ if true {
+ _index = 1
+ }
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ let mut _index = 1;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index += 1;
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index *= 2;
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index = 1;
+ _index += 1
+ }
+
+ let mut _index = 0;
+
+ for _v in &vec {
+ let mut _index = 0;
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index += 1;
+ _index = 0;
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ for _x in 0..1 {
+ _index += 1;
+ }
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for x in &vec {
+ if *x == 1 {
+ _index += 1
+ }
+ }
+
+ let mut _index = 0;
+ if true {
+ _index = 1
+ };
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 1;
+ if false {
+ _index = 0
+ };
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut index = 0;
+ {
+ let mut _x = &mut index;
+ }
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut index = 0;
+ for _v in &vec {
+ index += 1
+ }
+ println!("index: {}", index);
+
+ fn f<T>(_: &T, _: &T) -> bool {
+ unimplemented!()
+ }
+ fn g<T>(_: &mut [T], _: usize, _: usize) {
+ unimplemented!()
+ }
+ for i in 1..vec.len() {
+ if f(&vec[i - 1], &vec[i]) {
+ g(&mut vec, i - 1, i);
+ }
+ }
+
+ for mid in 1..vec.len() {
+ let (_, _) = vec.split_at(mid);
+ }
+}
+
+fn partition<T: PartialOrd + Send>(v: &mut [T]) -> usize {
+ let pivot = v.len() - 1;
+ let mut i = 0;
+ for j in 0..pivot {
+ if v[j] <= v[pivot] {
+ v.swap(i, j);
+ i += 1;
+ }
+ }
+ v.swap(i, pivot);
+ i
+}
+
+#[warn(clippy::needless_range_loop)]
+pub fn manual_copy_same_destination(dst: &mut [i32], d: usize, s: usize) {
+ // Same source and destination - don't trigger lint
+ for i in 0..dst.len() {
+ dst[d + i] = dst[s + i];
+ }
+}
+
+mod issue_2496 {
+ pub trait Handle {
+ fn new_for_index(index: usize) -> Self;
+ fn index(&self) -> usize;
+ }
+
+ pub fn test<H: Handle>() -> H {
+ for x in 0..5 {
+ let next_handle = H::new_for_index(x);
+ println!("{}", next_handle.index());
+ }
+ unimplemented!()
+ }
+}
+
+// explicit_into_iter_loop bad suggestions
+#[warn(clippy::explicit_into_iter_loop, clippy::explicit_iter_loop)]
+mod issue_4958 {
+ fn takes_iterator<T>(iterator: &T)
+ where
+ for<'a> &'a T: IntoIterator<Item = &'a String>,
+ {
+ for i in iterator {
+ println!("{}", i);
+ }
+ }
+
+ struct T;
+ impl IntoIterator for &T {
+ type Item = ();
+ type IntoIter = std::vec::IntoIter<Self::Item>;
+ fn into_iter(self) -> Self::IntoIter {
+ vec![].into_iter()
+ }
+ }
+
+ fn more_tests() {
+ let t = T;
+ let r = &t;
+ let rr = &&t;
+
+ // This case is handled by `explicit_iter_loop`. No idea why.
+ for _ in &t {}
+
+ for _ in r {}
+
+ // No suggestion for this.
+ // We'd have to suggest `for _ in *rr {}` which is less clear.
+ for _ in rr.into_iter() {}
+ }
+}
+
+// explicit_into_iter_loop
+#[warn(clippy::explicit_into_iter_loop)]
+mod issue_6900 {
+ struct S;
+ impl S {
+ #[allow(clippy::should_implement_trait)]
+ pub fn into_iter<T>(self) -> I<T> {
+ unimplemented!()
+ }
+ }
+
+ struct I<T>(T);
+ impl<T> Iterator for I<T> {
+ type Item = T;
+ fn next(&mut self) -> Option<Self::Item> {
+ unimplemented!()
+ }
+ }
+
+ fn f() {
+ for _ in S.into_iter::<u32>() {
+ unimplemented!()
+ }
+ }
+}
--- /dev/null
-
+// run-rustfix
+#![allow(dead_code, unused)]
++#![allow(clippy::uninlined_format_args)]
+
+use std::collections::*;
+
+#[warn(clippy::all)]
+struct Unrelated(Vec<u8>);
+impl Unrelated {
+ fn next(&self) -> std::slice::Iter<u8> {
+ self.0.iter()
+ }
+
+ fn iter(&self) -> std::slice::Iter<u8> {
+ self.0.iter()
+ }
+}
+
+#[warn(
+ clippy::needless_range_loop,
+ clippy::explicit_iter_loop,
+ clippy::explicit_into_iter_loop,
+ clippy::iter_next_loop,
+ clippy::for_kv_map
+)]
+#[allow(
+ clippy::linkedlist,
+ clippy::unnecessary_mut_passed,
+ clippy::similar_names,
+ clippy::needless_borrow
+)]
+#[allow(unused_variables)]
+fn main() {
+ let mut vec = vec![1, 2, 3, 4];
+
+ // See #601
+ for i in 0..10 {
+ // no error, id_col does not exist outside the loop
+ let mut id_col = vec![0f64; 10];
+ id_col[i] = 1f64;
+ }
+
+ for _v in vec.iter() {}
+
+ for _v in vec.iter_mut() {}
+
+ let out_vec = vec![1, 2, 3];
+ for _v in out_vec.into_iter() {}
+
+ for _v in &vec {} // these are fine
+ for _v in &mut vec {} // these are fine
+
+ for _v in [1, 2, 3].iter() {}
+
+ for _v in (&mut [1, 2, 3]).iter() {} // no error
+
+ for _v in [0; 32].iter() {}
+
+ for _v in [0; 33].iter() {} // no error
+
+ let ll: LinkedList<()> = LinkedList::new();
+ for _v in ll.iter() {}
+
+ let vd: VecDeque<()> = VecDeque::new();
+ for _v in vd.iter() {}
+
+ let bh: BinaryHeap<()> = BinaryHeap::new();
+ for _v in bh.iter() {}
+
+ let hm: HashMap<(), ()> = HashMap::new();
+ for _v in hm.iter() {}
+
+ let bt: BTreeMap<(), ()> = BTreeMap::new();
+ for _v in bt.iter() {}
+
+ let hs: HashSet<()> = HashSet::new();
+ for _v in hs.iter() {}
+
+ let bs: BTreeSet<()> = BTreeSet::new();
+ for _v in bs.iter() {}
+
+ let u = Unrelated(vec![]);
+ for _v in u.next() {} // no error
+ for _v in u.iter() {} // no error
+
+ let mut out = vec![];
+ vec.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>();
+ let _y = vec.iter().cloned().map(|x| out.push(x)).collect::<Vec<_>>(); // this is fine
+
+ // Loop with explicit counter variable
+
+ // Potential false positives
+ let mut _index = 0;
+ _index = 1;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ _index += 1;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ if true {
+ _index = 1
+ }
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ let mut _index = 1;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index += 1;
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index *= 2;
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index = 1;
+ _index += 1
+ }
+
+ let mut _index = 0;
+
+ for _v in &vec {
+ let mut _index = 0;
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ _index += 1;
+ _index = 0;
+ }
+
+ let mut _index = 0;
+ for _v in &vec {
+ for _x in 0..1 {
+ _index += 1;
+ }
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for x in &vec {
+ if *x == 1 {
+ _index += 1
+ }
+ }
+
+ let mut _index = 0;
+ if true {
+ _index = 1
+ };
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 1;
+ if false {
+ _index = 0
+ };
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut index = 0;
+ {
+ let mut _x = &mut index;
+ }
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut index = 0;
+ for _v in &vec {
+ index += 1
+ }
+ println!("index: {}", index);
+
+ fn f<T>(_: &T, _: &T) -> bool {
+ unimplemented!()
+ }
+ fn g<T>(_: &mut [T], _: usize, _: usize) {
+ unimplemented!()
+ }
+ for i in 1..vec.len() {
+ if f(&vec[i - 1], &vec[i]) {
+ g(&mut vec, i - 1, i);
+ }
+ }
+
+ for mid in 1..vec.len() {
+ let (_, _) = vec.split_at(mid);
+ }
+}
+
+fn partition<T: PartialOrd + Send>(v: &mut [T]) -> usize {
+ let pivot = v.len() - 1;
+ let mut i = 0;
+ for j in 0..pivot {
+ if v[j] <= v[pivot] {
+ v.swap(i, j);
+ i += 1;
+ }
+ }
+ v.swap(i, pivot);
+ i
+}
+
+#[warn(clippy::needless_range_loop)]
+pub fn manual_copy_same_destination(dst: &mut [i32], d: usize, s: usize) {
+ // Same source and destination - don't trigger lint
+ for i in 0..dst.len() {
+ dst[d + i] = dst[s + i];
+ }
+}
+
+mod issue_2496 {
+ pub trait Handle {
+ fn new_for_index(index: usize) -> Self;
+ fn index(&self) -> usize;
+ }
+
+ pub fn test<H: Handle>() -> H {
+ for x in 0..5 {
+ let next_handle = H::new_for_index(x);
+ println!("{}", next_handle.index());
+ }
+ unimplemented!()
+ }
+}
+
+// explicit_into_iter_loop bad suggestions
+#[warn(clippy::explicit_into_iter_loop, clippy::explicit_iter_loop)]
+mod issue_4958 {
+ fn takes_iterator<T>(iterator: &T)
+ where
+ for<'a> &'a T: IntoIterator<Item = &'a String>,
+ {
+ for i in iterator.into_iter() {
+ println!("{}", i);
+ }
+ }
+
+ struct T;
+ impl IntoIterator for &T {
+ type Item = ();
+ type IntoIter = std::vec::IntoIter<Self::Item>;
+ fn into_iter(self) -> Self::IntoIter {
+ vec![].into_iter()
+ }
+ }
+
+ fn more_tests() {
+ let t = T;
+ let r = &t;
+ let rr = &&t;
+
+ // This case is handled by `explicit_iter_loop`. No idea why.
+ for _ in t.into_iter() {}
+
+ for _ in r.into_iter() {}
+
+ // No suggestion for this.
+ // We'd have to suggest `for _ in *rr {}` which is less clear.
+ for _ in rr.into_iter() {}
+ }
+}
+
+// explicit_into_iter_loop
+#[warn(clippy::explicit_into_iter_loop)]
+mod issue_6900 {
+ struct S;
+ impl S {
+ #[allow(clippy::should_implement_trait)]
+ pub fn into_iter<T>(self) -> I<T> {
+ unimplemented!()
+ }
+ }
+
+ struct I<T>(T);
+ impl<T> Iterator for I<T> {
+ type Item = T;
+ fn next(&mut self) -> Option<Self::Item> {
+ unimplemented!()
+ }
+ }
+
+ fn f() {
+ for _ in S.into_iter::<u32>() {
+ unimplemented!()
+ }
+ }
+}
--- /dev/null
+#![warn(clippy::for_loops_over_fallibles)]
++#![allow(clippy::uninlined_format_args)]
+
+fn for_loops_over_fallibles() {
+ let option = Some(1);
+ let mut result = option.ok_or("x not found");
+ let v = vec![0, 1, 2];
+
+ // check over an `Option`
+ for x in option {
+ println!("{}", x);
+ }
+
+ // check over an `Option`
+ for x in option.iter() {
+ println!("{}", x);
+ }
+
+ // check over a `Result`
+ for x in result {
+ println!("{}", x);
+ }
+
+ // check over a `Result`
+ for x in result.iter_mut() {
+ println!("{}", x);
+ }
+
+ // check over a `Result`
+ for x in result.into_iter() {
+ println!("{}", x);
+ }
+
+ for x in option.ok_or("x not found") {
+ println!("{}", x);
+ }
+
+ // make sure LOOP_OVER_NEXT lint takes clippy::precedence when next() is the last call
+ // in the chain
+ for x in v.iter().next() {
+ println!("{}", x);
+ }
+
+ // make sure we lint when next() is not the last call in the chain
+ for x in v.iter().next().and(Some(0)) {
+ println!("{}", x);
+ }
+
+ for x in v.iter().next().ok_or("x not found") {
+ println!("{}", x);
+ }
+
+ // check for false positives
+
+ // for loop false positive
+ for x in v {
+ println!("{}", x);
+ }
+
+ // while let false positive for Option
+ while let Some(x) = option {
+ println!("{}", x);
+ break;
+ }
+
+ // while let false positive for Result
+ while let Ok(x) = result {
+ println!("{}", x);
+ break;
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/for_loops_over_fallibles.rs:9:14
+error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:14:14
++ --> $DIR/for_loops_over_fallibles.rs:10:14
+ |
+LL | for x in option {
+ | ^^^^^^
+ |
+ = help: consider replacing `for x in option` with `if let Some(x) = option`
+ = note: `-D clippy::for-loops-over-fallibles` implied by `-D warnings`
+
+error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:19:14
++ --> $DIR/for_loops_over_fallibles.rs:15:14
+ |
+LL | for x in option.iter() {
+ | ^^^^^^
+ |
+ = help: consider replacing `for x in option.iter()` with `if let Some(x) = option`
+
+error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:24:14
++ --> $DIR/for_loops_over_fallibles.rs:20:14
+ |
+LL | for x in result {
+ | ^^^^^^
+ |
+ = help: consider replacing `for x in result` with `if let Ok(x) = result`
+
+error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:29:14
++ --> $DIR/for_loops_over_fallibles.rs:25:14
+ |
+LL | for x in result.iter_mut() {
+ | ^^^^^^
+ |
+ = help: consider replacing `for x in result.iter_mut()` with `if let Ok(x) = result`
+
+error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:33:14
++ --> $DIR/for_loops_over_fallibles.rs:30:14
+ |
+LL | for x in result.into_iter() {
+ | ^^^^^^
+ |
+ = help: consider replacing `for x in result.into_iter()` with `if let Ok(x) = result`
+
+error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:39:14
++ --> $DIR/for_loops_over_fallibles.rs:34:14
+ |
+LL | for x in option.ok_or("x not found") {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")`
+
+error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want
- --> $DIR/for_loops_over_fallibles.rs:44:14
++ --> $DIR/for_loops_over_fallibles.rs:40:14
+ |
+LL | for x in v.iter().next() {
+ | ^^^^^^^^^^^^^^^
+ |
+ = note: `#[deny(clippy::iter_next_loop)]` on by default
+
+error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:48:14
++ --> $DIR/for_loops_over_fallibles.rs:45:14
+ |
+LL | for x in v.iter().next().and(Some(0)) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))`
+
+error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement
- --> $DIR/for_loops_over_fallibles.rs:60:5
++ --> $DIR/for_loops_over_fallibles.rs:49:14
+ |
+LL | for x in v.iter().next().ok_or("x not found") {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")`
+
+error: this loop never actually loops
- --> $DIR/for_loops_over_fallibles.rs:66:5
++ --> $DIR/for_loops_over_fallibles.rs:61:5
+ |
+LL | / while let Some(x) = option {
+LL | | println!("{}", x);
+LL | | break;
+LL | | }
+ | |_____^
+ |
+ = note: `#[deny(clippy::never_loop)]` on by default
+
+error: this loop never actually loops
++ --> $DIR/for_loops_over_fallibles.rs:67:5
+ |
+LL | / while let Ok(x) = result {
+LL | | println!("{}", x);
+LL | | break;
+LL | | }
+ | |_____^
+
+error: aborting due to 11 previous errors
+
--- /dev/null
-
+// run-rustfix
- clippy::needless_borrow
++#![warn(clippy::useless_format)]
+#![allow(
+ unused_tuple_struct_fields,
+ clippy::print_literal,
+ clippy::redundant_clone,
+ clippy::to_string_in_format_args,
- #![warn(clippy::useless_format)]
++ clippy::needless_borrow,
++ clippy::uninlined_format_args
+)]
+
+struct Foo(pub String);
+
+macro_rules! foo {
+ ($($t:tt)*) => (Foo(format!($($t)*)))
+}
+
+fn main() {
+ "foo".to_string();
+ "{}".to_string();
+ "{} abc {}".to_string();
+ r##"foo {}
+" bar"##.to_string();
+
+ let _ = String::new();
+
+ "foo".to_string();
+ format!("{:?}", "foo"); // Don't warn about `Debug`.
+ format!("{:8}", "foo");
+ format!("{:width$}", "foo", width = 8);
+ format!("foo {}", "bar");
+ format!("{} bar", "foo");
+
+ let arg = String::new();
+ arg.to_string();
+ format!("{:?}", arg); // Don't warn about debug.
+ format!("{:8}", arg);
+ format!("{:width$}", arg, width = 8);
+ format!("foo {}", arg);
+ format!("{} bar", arg);
+
+ // We don’t want to warn for non-string args; see issue #697.
+ format!("{}", 42);
+ format!("{:?}", 42);
+ format!("{:+}", 42);
+ format!("foo {}", 42);
+ format!("{} bar", 42);
+
+ // We only want to warn about `format!` itself.
+ println!("foo");
+ println!("{}", "foo");
+ println!("foo {}", "foo");
+ println!("{}", 42);
+ println!("foo {}", 42);
+
+ // A `format!` inside a macro should not trigger a warning.
+ foo!("should not warn");
+
+ // Precision on string means slicing without panicking on size.
+ format!("{:.1}", "foo"); // Could be `"foo"[..1]`
+ format!("{:.10}", "foo"); // Could not be `"foo"[..10]`
+ format!("{:.prec$}", "foo", prec = 1);
+ format!("{:.prec$}", "foo", prec = 10);
+
+ 42.to_string();
+ let x = std::path::PathBuf::from("/bar/foo/qux");
+ x.display().to_string();
+
+ // False positive
+ let a = "foo".to_string();
+ let _ = Some(a + "bar");
+
+ // Wrap it with braces
+ let v: Vec<String> = vec!["foo".to_string(), "bar".to_string()];
+ let _s: String = (&*v.join("\n")).to_string();
+
+ format!("prepend {:+}", "s");
+
+ // Issue #8290
+ let x = "foo";
+ let _ = x.to_string();
+ let _ = format!("{x:?}"); // Don't lint on debug
+ let _ = x.to_string();
+
+ // Issue #9234
+ let abc = "abc";
+ let _ = abc.to_string();
+ let xx = "xx";
+ let _ = xx.to_string();
+}
--- /dev/null
-
+// run-rustfix
- clippy::needless_borrow
++#![warn(clippy::useless_format)]
+#![allow(
+ unused_tuple_struct_fields,
+ clippy::print_literal,
+ clippy::redundant_clone,
+ clippy::to_string_in_format_args,
- #![warn(clippy::useless_format)]
++ clippy::needless_borrow,
++ clippy::uninlined_format_args
+)]
+
+struct Foo(pub String);
+
+macro_rules! foo {
+ ($($t:tt)*) => (Foo(format!($($t)*)))
+}
+
+fn main() {
+ format!("foo");
+ format!("{{}}");
+ format!("{{}} abc {{}}");
+ format!(
+ r##"foo {{}}
+" bar"##
+ );
+
+ let _ = format!("");
+
+ format!("{}", "foo");
+ format!("{:?}", "foo"); // Don't warn about `Debug`.
+ format!("{:8}", "foo");
+ format!("{:width$}", "foo", width = 8);
+ format!("foo {}", "bar");
+ format!("{} bar", "foo");
+
+ let arg = String::new();
+ format!("{}", arg);
+ format!("{:?}", arg); // Don't warn about debug.
+ format!("{:8}", arg);
+ format!("{:width$}", arg, width = 8);
+ format!("foo {}", arg);
+ format!("{} bar", arg);
+
+ // We don’t want to warn for non-string args; see issue #697.
+ format!("{}", 42);
+ format!("{:?}", 42);
+ format!("{:+}", 42);
+ format!("foo {}", 42);
+ format!("{} bar", 42);
+
+ // We only want to warn about `format!` itself.
+ println!("foo");
+ println!("{}", "foo");
+ println!("foo {}", "foo");
+ println!("{}", 42);
+ println!("foo {}", 42);
+
+ // A `format!` inside a macro should not trigger a warning.
+ foo!("should not warn");
+
+ // Precision on string means slicing without panicking on size.
+ format!("{:.1}", "foo"); // Could be `"foo"[..1]`
+ format!("{:.10}", "foo"); // Could not be `"foo"[..10]`
+ format!("{:.prec$}", "foo", prec = 1);
+ format!("{:.prec$}", "foo", prec = 10);
+
+ format!("{}", 42.to_string());
+ let x = std::path::PathBuf::from("/bar/foo/qux");
+ format!("{}", x.display().to_string());
+
+ // False positive
+ let a = "foo".to_string();
+ let _ = Some(format!("{}", a + "bar"));
+
+ // Wrap it with braces
+ let v: Vec<String> = vec!["foo".to_string(), "bar".to_string()];
+ let _s: String = format!("{}", &*v.join("\n"));
+
+ format!("prepend {:+}", "s");
+
+ // Issue #8290
+ let x = "foo";
+ let _ = format!("{x}");
+ let _ = format!("{x:?}"); // Don't lint on debug
+ let _ = format!("{y}", y = x);
+
+ // Issue #9234
+ let abc = "abc";
+ let _ = format!("{abc}");
+ let xx = "xx";
+ let _ = format!("{xx}");
+}
--- /dev/null
-
- #![allow(unused)]
- #![allow(clippy::assertions_on_constants)]
- #![allow(clippy::eq_op)]
- #![allow(clippy::print_literal)]
+// run-rustfix
+#![warn(clippy::to_string_in_format_args)]
++#![allow(unused)]
++#![allow(
++ clippy::assertions_on_constants,
++ clippy::eq_op,
++ clippy::print_literal,
++ clippy::uninlined_format_args
++)]
+
+use std::io::{stdout, Write};
+use std::ops::Deref;
+use std::panic::Location;
+
+struct Somewhere;
+
+impl ToString for Somewhere {
+ fn to_string(&self) -> String {
+ String::from("somewhere")
+ }
+}
+
+struct X(u32);
+
+impl Deref for X {
+ type Target = u32;
+
+ fn deref(&self) -> &u32 {
+ &self.0
+ }
+}
+
+struct Y<'a>(&'a X);
+
+impl<'a> Deref for Y<'a> {
+ type Target = &'a X;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+struct Z(u32);
+
+impl Deref for Z {
+ type Target = u32;
+
+ fn deref(&self) -> &u32 {
+ &self.0
+ }
+}
+
+impl std::fmt::Display for Z {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Z")
+ }
+}
+
+macro_rules! my_macro {
+ () => {
+ // here be dragons, do not enter (or lint)
+ println!("error: something failed at {}", Location::caller().to_string());
+ };
+}
+
+macro_rules! my_other_macro {
+ () => {
+ Location::caller().to_string()
+ };
+}
+
+fn main() {
+ let x = &X(1);
+ let x_ref = &x;
+
+ let _ = format!("error: something failed at {}", Location::caller());
+ let _ = write!(
+ stdout(),
+ "error: something failed at {}",
+ Location::caller()
+ );
+ let _ = writeln!(
+ stdout(),
+ "error: something failed at {}",
+ Location::caller()
+ );
+ print!("error: something failed at {}", Location::caller());
+ println!("error: something failed at {}", Location::caller());
+ eprint!("error: something failed at {}", Location::caller());
+ eprintln!("error: something failed at {}", Location::caller());
+ let _ = format_args!("error: something failed at {}", Location::caller());
+ assert!(true, "error: something failed at {}", Location::caller());
+ assert_eq!(0, 0, "error: something failed at {}", Location::caller());
+ assert_ne!(0, 0, "error: something failed at {}", Location::caller());
+ panic!("error: something failed at {}", Location::caller());
+ println!("{}", *X(1));
+ println!("{}", ***Y(&X(1)));
+ println!("{}", Z(1));
+ println!("{}", **x);
+ println!("{}", ***x_ref);
+ // https://github.com/rust-lang/rust-clippy/issues/7903
+ println!("{foo}{bar}", foo = "foo", bar = "bar");
+ println!("{foo}{bar}", foo = "foo", bar = "bar");
+ println!("{foo}{bar}", bar = "bar", foo = "foo");
+ println!("{foo}{bar}", bar = "bar", foo = "foo");
+
+ // negative tests
+ println!("error: something failed at {}", Somewhere.to_string());
+ // The next two tests are negative because caching the string might be faster than calling `<X as
+ // Display>::fmt` twice.
+ println!("{} and again {0}", x.to_string());
+ println!("{foo}{foo}", foo = "foo".to_string());
+ my_macro!();
+ println!("error: something failed at {}", my_other_macro!());
+ // https://github.com/rust-lang/rust-clippy/issues/7903
+ println!("{foo}{foo:?}", foo = "foo".to_string());
+}
+
+fn issue8643(vendor_id: usize, product_id: usize, name: &str) {
+ println!(
+ "{:<9} {:<10} {}",
+ format!("0x{:x}", vendor_id),
+ format!("0x{:x}", product_id),
+ name
+ );
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/8855
+mod issue_8855 {
+ #![allow(dead_code)]
+
+ struct A {}
+
+ impl std::fmt::Display for A {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "test")
+ }
+ }
+
+ fn main() {
+ let a = A {};
+ let b = A {};
+
+ let x = format!("{} {}", a, b);
+ dbg!(x);
+
+ let x = format!("{:>6} {:>6}", a, b.to_string());
+ dbg!(x);
+ }
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/9256
+mod issue_9256 {
+ #![allow(dead_code)]
+
+ fn print_substring(original: &str) {
+ assert!(original.len() > 10);
+ println!("{}", &original[..10]);
+ }
+
+ fn main() {
+ print_substring("Hello, world!");
+ }
+}
--- /dev/null
-
- #![allow(unused)]
- #![allow(clippy::assertions_on_constants)]
- #![allow(clippy::eq_op)]
- #![allow(clippy::print_literal)]
+// run-rustfix
+#![warn(clippy::to_string_in_format_args)]
++#![allow(unused)]
++#![allow(
++ clippy::assertions_on_constants,
++ clippy::eq_op,
++ clippy::print_literal,
++ clippy::uninlined_format_args
++)]
+
+use std::io::{stdout, Write};
+use std::ops::Deref;
+use std::panic::Location;
+
+struct Somewhere;
+
+impl ToString for Somewhere {
+ fn to_string(&self) -> String {
+ String::from("somewhere")
+ }
+}
+
+struct X(u32);
+
+impl Deref for X {
+ type Target = u32;
+
+ fn deref(&self) -> &u32 {
+ &self.0
+ }
+}
+
+struct Y<'a>(&'a X);
+
+impl<'a> Deref for Y<'a> {
+ type Target = &'a X;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+struct Z(u32);
+
+impl Deref for Z {
+ type Target = u32;
+
+ fn deref(&self) -> &u32 {
+ &self.0
+ }
+}
+
+impl std::fmt::Display for Z {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Z")
+ }
+}
+
+macro_rules! my_macro {
+ () => {
+ // here be dragons, do not enter (or lint)
+ println!("error: something failed at {}", Location::caller().to_string());
+ };
+}
+
+macro_rules! my_other_macro {
+ () => {
+ Location::caller().to_string()
+ };
+}
+
+fn main() {
+ let x = &X(1);
+ let x_ref = &x;
+
+ let _ = format!("error: something failed at {}", Location::caller().to_string());
+ let _ = write!(
+ stdout(),
+ "error: something failed at {}",
+ Location::caller().to_string()
+ );
+ let _ = writeln!(
+ stdout(),
+ "error: something failed at {}",
+ Location::caller().to_string()
+ );
+ print!("error: something failed at {}", Location::caller().to_string());
+ println!("error: something failed at {}", Location::caller().to_string());
+ eprint!("error: something failed at {}", Location::caller().to_string());
+ eprintln!("error: something failed at {}", Location::caller().to_string());
+ let _ = format_args!("error: something failed at {}", Location::caller().to_string());
+ assert!(true, "error: something failed at {}", Location::caller().to_string());
+ assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
+ assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
+ panic!("error: something failed at {}", Location::caller().to_string());
+ println!("{}", X(1).to_string());
+ println!("{}", Y(&X(1)).to_string());
+ println!("{}", Z(1).to_string());
+ println!("{}", x.to_string());
+ println!("{}", x_ref.to_string());
+ // https://github.com/rust-lang/rust-clippy/issues/7903
+ println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
+ println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
+ println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
+ println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
+
+ // negative tests
+ println!("error: something failed at {}", Somewhere.to_string());
+ // The next two tests are negative because caching the string might be faster than calling `<X as
+ // Display>::fmt` twice.
+ println!("{} and again {0}", x.to_string());
+ println!("{foo}{foo}", foo = "foo".to_string());
+ my_macro!();
+ println!("error: something failed at {}", my_other_macro!());
+ // https://github.com/rust-lang/rust-clippy/issues/7903
+ println!("{foo}{foo:?}", foo = "foo".to_string());
+}
+
+fn issue8643(vendor_id: usize, product_id: usize, name: &str) {
+ println!(
+ "{:<9} {:<10} {}",
+ format!("0x{:x}", vendor_id),
+ format!("0x{:x}", product_id),
+ name
+ );
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/8855
+mod issue_8855 {
+ #![allow(dead_code)]
+
+ struct A {}
+
+ impl std::fmt::Display for A {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "test")
+ }
+ }
+
+ fn main() {
+ let a = A {};
+ let b = A {};
+
+ let x = format!("{} {}", a, b.to_string());
+ dbg!(x);
+
+ let x = format!("{:>6} {:>6}", a, b.to_string());
+ dbg!(x);
+ }
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/9256
+mod issue_9256 {
+ #![allow(dead_code)]
+
+ fn print_substring(original: &str) {
+ assert!(original.len() > 10);
+ println!("{}", original[..10].to_string());
+ }
+
+ fn main() {
+ print_substring("Hello, world!");
+ }
+}
--- /dev/null
- --> $DIR/format_args.rs:74:72
+error: `to_string` applied to a type that implements `Display` in `format!` args
- --> $DIR/format_args.rs:78:27
++ --> $DIR/format_args.rs:76:72
+ |
+LL | let _ = format!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+ |
+ = note: `-D clippy::to-string-in-format-args` implied by `-D warnings`
+
+error: `to_string` applied to a type that implements `Display` in `write!` args
- --> $DIR/format_args.rs:83:27
++ --> $DIR/format_args.rs:80:27
+ |
+LL | Location::caller().to_string()
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `writeln!` args
- --> $DIR/format_args.rs:85:63
++ --> $DIR/format_args.rs:85:27
+ |
+LL | Location::caller().to_string()
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `print!` args
- --> $DIR/format_args.rs:86:65
++ --> $DIR/format_args.rs:87:63
+ |
+LL | print!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:87:64
++ --> $DIR/format_args.rs:88:65
+ |
+LL | println!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `eprint!` args
- --> $DIR/format_args.rs:88:66
++ --> $DIR/format_args.rs:89:64
+ |
+LL | eprint!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `eprintln!` args
- --> $DIR/format_args.rs:89:77
++ --> $DIR/format_args.rs:90:66
+ |
+LL | eprintln!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `format_args!` args
- --> $DIR/format_args.rs:90:70
++ --> $DIR/format_args.rs:91:77
+ |
+LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `assert!` args
- --> $DIR/format_args.rs:91:73
++ --> $DIR/format_args.rs:92:70
+ |
+LL | assert!(true, "error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `assert_eq!` args
- --> $DIR/format_args.rs:92:73
++ --> $DIR/format_args.rs:93:73
+ |
+LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `assert_ne!` args
- --> $DIR/format_args.rs:93:63
++ --> $DIR/format_args.rs:94:73
+ |
+LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `panic!` args
- --> $DIR/format_args.rs:94:20
++ --> $DIR/format_args.rs:95:63
+ |
+LL | panic!("error: something failed at {}", Location::caller().to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:95:20
++ --> $DIR/format_args.rs:96:20
+ |
+LL | println!("{}", X(1).to_string());
+ | ^^^^^^^^^^^^^^^^ help: use this: `*X(1)`
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:96:24
++ --> $DIR/format_args.rs:97:20
+ |
+LL | println!("{}", Y(&X(1)).to_string());
+ | ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))`
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:97:20
++ --> $DIR/format_args.rs:98:24
+ |
+LL | println!("{}", Z(1).to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:98:20
++ --> $DIR/format_args.rs:99:20
+ |
+LL | println!("{}", x.to_string());
+ | ^^^^^^^^^^^^^ help: use this: `**x`
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:100:39
++ --> $DIR/format_args.rs:100:20
+ |
+LL | println!("{}", x_ref.to_string());
+ | ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:101:52
++ --> $DIR/format_args.rs:102:39
+ |
+LL | println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:102:39
++ --> $DIR/format_args.rs:103:52
+ |
+LL | println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:103:52
++ --> $DIR/format_args.rs:104:39
+ |
+LL | println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
- --> $DIR/format_args.rs:142:38
++ --> $DIR/format_args.rs:105:52
+ |
+LL | println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `format!` args
- --> $DIR/format_args.rs:156:24
++ --> $DIR/format_args.rs:144:38
+ |
+LL | let x = format!("{} {}", a, b.to_string());
+ | ^^^^^^^^^^^^ help: remove this
+
+error: `to_string` applied to a type that implements `Display` in `println!` args
++ --> $DIR/format_args.rs:158:24
+ |
+LL | println!("{}", original[..10].to_string());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use this: `&original[..10]`
+
+error: aborting due to 23 previous errors
+
--- /dev/null
- #![allow(clippy::assertions_on_constants)]
- #![allow(clippy::eq_op)]
- #![warn(clippy::format_in_format_args)]
- #![warn(clippy::to_string_in_format_args)]
++#![warn(clippy::format_in_format_args, clippy::to_string_in_format_args)]
++#![allow(clippy::assertions_on_constants, clippy::eq_op, clippy::uninlined_format_args)]
+
+use std::io::{stdout, Error, ErrorKind, Write};
+use std::ops::Deref;
+use std::panic::Location;
+
+macro_rules! my_macro {
+ () => {
+ // here be dragons, do not enter (or lint)
+ println!("error: {}", format!("something failed at {}", Location::caller()));
+ };
+}
+
+macro_rules! my_other_macro {
+ () => {
+ format!("something failed at {}", Location::caller())
+ };
+}
+
+fn main() {
+ let error = Error::new(ErrorKind::Other, "bad thing");
+ let x = 'x';
+
+ println!("error: {}", format!("something failed at {}", Location::caller()));
+ println!("{}: {}", error, format!("something failed at {}", Location::caller()));
+ println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
+ println!("{{}}: {}", format!("something failed at {}", Location::caller()));
+ println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
+ println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
+ println!("error: {}", format!("something failed at {} {0}", Location::caller()));
+ let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
+ let _ = write!(
+ stdout(),
+ "error: {}",
+ format!("something failed at {}", Location::caller())
+ );
+ let _ = writeln!(
+ stdout(),
+ "error: {}",
+ format!("something failed at {}", Location::caller())
+ );
+ print!("error: {}", format!("something failed at {}", Location::caller()));
+ eprint!("error: {}", format!("something failed at {}", Location::caller()));
+ eprintln!("error: {}", format!("something failed at {}", Location::caller()));
+ let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
+ assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
+ assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
+ assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
+ panic!("error: {}", format!("something failed at {}", Location::caller()));
+
+ // negative tests
+ println!("error: {}", format_args!("something failed at {}", Location::caller()));
+ println!("error: {:>70}", format!("something failed at {}", Location::caller()));
+ println!("error: {} {0}", format!("something failed at {}", Location::caller()));
+ println!("{} and again {0}", format!("hi {}", x));
+ my_macro!();
+ println!("error: {}", my_other_macro!());
+}
--- /dev/null
- --> $DIR/format_args_unfixable.rs:27:5
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:28:5
++ --> $DIR/format_args_unfixable.rs:25:5
+ |
+LL | println!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+ = note: `-D clippy::format-in-format-args` implied by `-D warnings`
+
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:29:5
++ --> $DIR/format_args_unfixable.rs:26:5
+ |
+LL | println!("{}: {}", error, format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:30:5
++ --> $DIR/format_args_unfixable.rs:27:5
+ |
+LL | println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:31:5
++ --> $DIR/format_args_unfixable.rs:28:5
+ |
+LL | println!("{{}}: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:32:5
++ --> $DIR/format_args_unfixable.rs:29:5
+ |
+LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:33:5
++ --> $DIR/format_args_unfixable.rs:30:5
+ |
+LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `println!` args
- --> $DIR/format_args_unfixable.rs:34:13
++ --> $DIR/format_args_unfixable.rs:31:5
+ |
+LL | println!("error: {}", format!("something failed at {} {0}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `println!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `format!` args
- --> $DIR/format_args_unfixable.rs:35:13
++ --> $DIR/format_args_unfixable.rs:32:13
+ |
+LL | let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `format!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `write!` args
- --> $DIR/format_args_unfixable.rs:40:13
++ --> $DIR/format_args_unfixable.rs:33:13
+ |
+LL | let _ = write!(
+ | _____________^
+LL | | stdout(),
+LL | | "error: {}",
+LL | | format!("something failed at {}", Location::caller())
+LL | | );
+ | |_____^
+ |
+ = help: combine the `format!(..)` arguments with the outer `write!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `writeln!` args
- --> $DIR/format_args_unfixable.rs:45:5
++ --> $DIR/format_args_unfixable.rs:38:13
+ |
+LL | let _ = writeln!(
+ | _____________^
+LL | | stdout(),
+LL | | "error: {}",
+LL | | format!("something failed at {}", Location::caller())
+LL | | );
+ | |_____^
+ |
+ = help: combine the `format!(..)` arguments with the outer `writeln!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `print!` args
- --> $DIR/format_args_unfixable.rs:46:5
++ --> $DIR/format_args_unfixable.rs:43:5
+ |
+LL | print!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `print!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `eprint!` args
- --> $DIR/format_args_unfixable.rs:47:5
++ --> $DIR/format_args_unfixable.rs:44:5
+ |
+LL | eprint!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `eprint!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `eprintln!` args
- --> $DIR/format_args_unfixable.rs:48:13
++ --> $DIR/format_args_unfixable.rs:45:5
+ |
+LL | eprintln!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `eprintln!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `format_args!` args
- --> $DIR/format_args_unfixable.rs:49:5
++ --> $DIR/format_args_unfixable.rs:46:13
+ |
+LL | let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `format_args!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `assert!` args
- --> $DIR/format_args_unfixable.rs:50:5
++ --> $DIR/format_args_unfixable.rs:47:5
+ |
+LL | assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `assert!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `assert_eq!` args
- --> $DIR/format_args_unfixable.rs:51:5
++ --> $DIR/format_args_unfixable.rs:48:5
+ |
+LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `assert_eq!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `assert_ne!` args
- --> $DIR/format_args_unfixable.rs:52:5
++ --> $DIR/format_args_unfixable.rs:49:5
+ |
+LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `assert_ne!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: `format!` in `panic!` args
++ --> $DIR/format_args_unfixable.rs:50:5
+ |
+LL | panic!("error: {}", format!("something failed at {}", Location::caller()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: combine the `format!(..)` arguments with the outer `panic!(..)` call
+ = help: or consider changing `format!` to `format_args!`
+
+error: aborting due to 18 previous errors
+
--- /dev/null
- #![allow(dead_code)]
- #![allow(unused_unsafe, clippy::missing_safety_doc)]
+#![warn(clippy::all)]
++#![allow(dead_code, unused_unsafe)]
++#![allow(clippy::missing_safety_doc, clippy::uninlined_format_args)]
+
+// TOO_MANY_ARGUMENTS
+fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {}
+
+fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {}
+
+#[rustfmt::skip]
+fn bad_multiline(
+ one: u32,
+ two: u32,
+ three: &str,
+ four: bool,
+ five: f32,
+ six: f32,
+ seven: bool,
+ eight: ()
+) {
+ let _one = one;
+ let _two = two;
+ let _three = three;
+ let _four = four;
+ let _five = five;
+ let _six = six;
+ let _seven = seven;
+}
+
+// don't lint extern fns
+extern "C" fn extern_fn(
+ _one: u32,
+ _two: u32,
+ _three: *const u8,
+ _four: bool,
+ _five: f32,
+ _six: f32,
+ _seven: bool,
+ _eight: *const std::ffi::c_void,
+) {
+}
+
+pub trait Foo {
+ fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool);
+ fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ());
+
+ fn ptr(p: *const u8);
+}
+
+pub struct Bar;
+
+impl Bar {
+ fn good_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {}
+ fn bad_method(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {}
+}
+
+// ok, we don’t want to warn implementations
+impl Foo for Bar {
+ fn good(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool) {}
+ fn bad(_one: u32, _two: u32, _three: &str, _four: bool, _five: f32, _six: f32, _seven: bool, _eight: ()) {}
+
+ fn ptr(p: *const u8) {
+ println!("{}", unsafe { *p });
+ println!("{:?}", unsafe { p.as_ref() });
+ unsafe { std::ptr::read(p) };
+ }
+}
+
+// NOT_UNSAFE_PTR_ARG_DEREF
+
+fn private(p: *const u8) {
+ println!("{}", unsafe { *p });
+}
+
+pub fn public(p: *const u8) {
+ println!("{}", unsafe { *p });
+ println!("{:?}", unsafe { p.as_ref() });
+ unsafe { std::ptr::read(p) };
+}
+
+type Alias = *const u8;
+
+pub fn type_alias(p: Alias) {
+ println!("{}", unsafe { *p });
+ println!("{:?}", unsafe { p.as_ref() });
+ unsafe { std::ptr::read(p) };
+}
+
+impl Bar {
+ fn private(self, p: *const u8) {
+ println!("{}", unsafe { *p });
+ }
+
+ pub fn public(self, p: *const u8) {
+ println!("{}", unsafe { *p });
+ println!("{:?}", unsafe { p.as_ref() });
+ unsafe { std::ptr::read(p) };
+ }
+
+ pub fn public_ok(self, p: *const u8) {
+ if !p.is_null() {
+ println!("{:p}", p);
+ }
+ }
+
+ pub unsafe fn public_unsafe(self, p: *const u8) {
+ println!("{}", unsafe { *p });
+ println!("{:?}", unsafe { p.as_ref() });
+ }
+}
+
+fn main() {}
--- /dev/null
-
+// run-rustfix
- unused
+#![warn(clippy::identity_op)]
++#![allow(unused)]
+#![allow(
+ clippy::eq_op,
+ clippy::no_effect,
+ clippy::unnecessary_operation,
+ clippy::op_ref,
+ clippy::double_parens,
++ clippy::uninlined_format_args
+)]
+
+use std::fmt::Write as _;
+
+const ONE: i64 = 1;
+const NEG_ONE: i64 = -1;
+const ZERO: i64 = 0;
+
+struct A(String);
+
+impl std::ops::Shl<i32> for A {
+ type Output = A;
+ fn shl(mut self, other: i32) -> Self {
+ let _ = write!(self.0, "{}", other);
+ self
+ }
+}
+
+struct Length(u8);
+struct Meter;
+
+impl core::ops::Mul<Meter> for u8 {
+ type Output = Length;
+ fn mul(self, _: Meter) -> Length {
+ Length(self)
+ }
+}
+
+#[rustfmt::skip]
+fn main() {
+ let x = 0;
+
+ x;
+ x;
+ x + 1;
+ x;
+ 1 + x;
+ x - ZERO; //no error, as we skip lookups (for now)
+ x;
+ ((ZERO)) | x; //no error, as we skip lookups (for now)
+
+ x;
+ x;
+ x / ONE; //no error, as we skip lookups (for now)
+
+ x / 2; //no false positive
+
+ x & NEG_ONE; //no error, as we skip lookups (for now)
+ x;
+
+ let u: u8 = 0;
+ u;
+
+ 1 << 0; // no error, this case is allowed, see issue 3430
+ 42;
+ 1;
+ 42;
+ &x;
+ x;
+
+ let mut a = A(String::new());
+ let b = a << 0; // no error: non-integer
+
+ 1 * Meter; // no error: non-integer
+
+ 2;
+ -2;
+ 2 + x;
+ -2 + x;
+ x + 1;
+ (x + 1) % 3; // no error
+ 4 % 3; // no error
+ 4 % -3; // no error
+
+ // See #8724
+ let a = 0;
+ let b = true;
+ (if b { 1 } else { 2 });
+ (if b { 1 } else { 2 }) + if b { 3 } else { 4 };
+ (match a { 0 => 10, _ => 20 });
+ (match a { 0 => 10, _ => 20 }) + match a { 0 => 30, _ => 40 };
+ (if b { 1 } else { 2 }) + match a { 0 => 30, _ => 40 };
+ (match a { 0 => 10, _ => 20 }) + if b { 3 } else { 4 };
+ (if b { 1 } else { 2 });
+
+ ({ a }) + 3;
+ ({ a } * 2);
+ (loop { let mut c = 0; if c == 10 { break c; } c += 1; }) + { a * 2 };
+
+ fn f(_: i32) {
+ todo!();
+ }
+ f(a + { 8 * 5 });
+ f(if b { 1 } else { 2 } + 3);
+ const _: i32 = { 2 * 4 } + 3;
+ const _: i32 = { 1 + 2 * 3 } + 3;
+
+ a as usize;
+ let _ = a as usize;
+ ({ a } as usize);
+
+ 2 * { a };
+ (({ a } + 4));
+ 1;
+}
+
+pub fn decide(a: bool, b: bool) -> u32 {
+ (if a { 1 } else { 2 }) + if b { 3 } else { 5 }
+}
--- /dev/null
-
+// run-rustfix
- unused
+#![warn(clippy::identity_op)]
++#![allow(unused)]
+#![allow(
+ clippy::eq_op,
+ clippy::no_effect,
+ clippy::unnecessary_operation,
+ clippy::op_ref,
+ clippy::double_parens,
++ clippy::uninlined_format_args
+)]
+
+use std::fmt::Write as _;
+
+const ONE: i64 = 1;
+const NEG_ONE: i64 = -1;
+const ZERO: i64 = 0;
+
+struct A(String);
+
+impl std::ops::Shl<i32> for A {
+ type Output = A;
+ fn shl(mut self, other: i32) -> Self {
+ let _ = write!(self.0, "{}", other);
+ self
+ }
+}
+
+struct Length(u8);
+struct Meter;
+
+impl core::ops::Mul<Meter> for u8 {
+ type Output = Length;
+ fn mul(self, _: Meter) -> Length {
+ Length(self)
+ }
+}
+
+#[rustfmt::skip]
+fn main() {
+ let x = 0;
+
+ x + 0;
+ x + (1 - 1);
+ x + 1;
+ 0 + x;
+ 1 + x;
+ x - ZERO; //no error, as we skip lookups (for now)
+ x | (0);
+ ((ZERO)) | x; //no error, as we skip lookups (for now)
+
+ x * 1;
+ 1 * x;
+ x / ONE; //no error, as we skip lookups (for now)
+
+ x / 2; //no false positive
+
+ x & NEG_ONE; //no error, as we skip lookups (for now)
+ -1 & x;
+
+ let u: u8 = 0;
+ u & 255;
+
+ 1 << 0; // no error, this case is allowed, see issue 3430
+ 42 << 0;
+ 1 >> 0;
+ 42 >> 0;
+ &x >> 0;
+ x >> &0;
+
+ let mut a = A(String::new());
+ let b = a << 0; // no error: non-integer
+
+ 1 * Meter; // no error: non-integer
+
+ 2 % 3;
+ -2 % 3;
+ 2 % -3 + x;
+ -2 % -3 + x;
+ x + 1 % 3;
+ (x + 1) % 3; // no error
+ 4 % 3; // no error
+ 4 % -3; // no error
+
+ // See #8724
+ let a = 0;
+ let b = true;
+ 0 + if b { 1 } else { 2 };
+ 0 + if b { 1 } else { 2 } + if b { 3 } else { 4 };
+ 0 + match a { 0 => 10, _ => 20 };
+ 0 + match a { 0 => 10, _ => 20 } + match a { 0 => 30, _ => 40 };
+ 0 + if b { 1 } else { 2 } + match a { 0 => 30, _ => 40 };
+ 0 + match a { 0 => 10, _ => 20 } + if b { 3 } else { 4 };
+ (if b { 1 } else { 2 }) + 0;
+
+ 0 + { a } + 3;
+ 0 + { a } * 2;
+ 0 + loop { let mut c = 0; if c == 10 { break c; } c += 1; } + { a * 2 };
+
+ fn f(_: i32) {
+ todo!();
+ }
+ f(1 * a + { 8 * 5 });
+ f(0 + if b { 1 } else { 2 } + 3);
+ const _: i32 = { 2 * 4 } + 0 + 3;
+ const _: i32 = 0 + { 1 + 2 * 3 } + 3;
+
+ 0 + a as usize;
+ let _ = 0 + a as usize;
+ 0 + { a } as usize;
+
+ 2 * (0 + { a });
+ 1 * ({ a } + 4);
+ 1 * 1;
+}
+
+pub fn decide(a: bool, b: bool) -> u32 {
+ 0 + if a { 1 } else { 2 } + if b { 3 } else { 5 }
+}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(unused)]
++#![warn(clippy::implicit_saturating_add)]
++
++fn main() {
++ let mut u_8: u8 = 255;
++ let mut u_16: u16 = 500;
++ let mut u_32: u32 = 7000;
++ let mut u_64: u64 = 7000;
++ let mut i_8: i8 = 30;
++ let mut i_16: i16 = 500;
++ let mut i_32: i32 = 7000;
++ let mut i_64: i64 = 7000;
++
++ if i_8 < 42 {
++ i_8 += 1;
++ }
++ if i_8 != 42 {
++ i_8 += 1;
++ }
++
++ u_8 = u_8.saturating_add(1);
++
++ u_8 = u_8.saturating_add(1);
++
++ if u_8 < 15 {
++ u_8 += 1;
++ }
++
++ u_16 = u_16.saturating_add(1);
++
++ u_16 = u_16.saturating_add(1);
++
++ u_16 = u_16.saturating_add(1);
++
++ u_32 = u_32.saturating_add(1);
++
++ u_32 = u_32.saturating_add(1);
++
++ u_32 = u_32.saturating_add(1);
++
++ u_64 = u_64.saturating_add(1);
++
++ u_64 = u_64.saturating_add(1);
++
++ u_64 = u_64.saturating_add(1);
++
++ i_8 = i_8.saturating_add(1);
++
++ i_8 = i_8.saturating_add(1);
++
++ i_8 = i_8.saturating_add(1);
++
++ i_16 = i_16.saturating_add(1);
++
++ i_16 = i_16.saturating_add(1);
++
++ i_16 = i_16.saturating_add(1);
++
++ i_32 = i_32.saturating_add(1);
++
++ i_32 = i_32.saturating_add(1);
++
++ i_32 = i_32.saturating_add(1);
++
++ i_64 = i_64.saturating_add(1);
++
++ i_64 = i_64.saturating_add(1);
++
++ i_64 = i_64.saturating_add(1);
++
++ if i_64 < 42 {
++ i_64 += 1;
++ }
++
++ if 42 > i_64 {
++ i_64 += 1;
++ }
++
++ let a = 12;
++ let mut b = 8;
++
++ if a < u8::MAX {
++ b += 1;
++ }
++
++ if u8::MAX > a {
++ b += 1;
++ }
++
++ if u_32 < u32::MAX {
++ u_32 += 1;
++ } else {
++ println!("don't lint this");
++ }
++
++ if u_32 < u32::MAX {
++ println!("don't lint this");
++ u_32 += 1;
++ }
++
++ if u_32 < 42 {
++ println!("brace yourself!");
++ } else {u_32 = u_32.saturating_add(1); }
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(unused)]
++#![warn(clippy::implicit_saturating_add)]
++
++fn main() {
++ let mut u_8: u8 = 255;
++ let mut u_16: u16 = 500;
++ let mut u_32: u32 = 7000;
++ let mut u_64: u64 = 7000;
++ let mut i_8: i8 = 30;
++ let mut i_16: i16 = 500;
++ let mut i_32: i32 = 7000;
++ let mut i_64: i64 = 7000;
++
++ if i_8 < 42 {
++ i_8 += 1;
++ }
++ if i_8 != 42 {
++ i_8 += 1;
++ }
++
++ if u_8 != u8::MAX {
++ u_8 += 1;
++ }
++
++ if u_8 < u8::MAX {
++ u_8 += 1;
++ }
++
++ if u_8 < 15 {
++ u_8 += 1;
++ }
++
++ if u_16 != u16::MAX {
++ u_16 += 1;
++ }
++
++ if u_16 < u16::MAX {
++ u_16 += 1;
++ }
++
++ if u16::MAX > u_16 {
++ u_16 += 1;
++ }
++
++ if u_32 != u32::MAX {
++ u_32 += 1;
++ }
++
++ if u_32 < u32::MAX {
++ u_32 += 1;
++ }
++
++ if u32::MAX > u_32 {
++ u_32 += 1;
++ }
++
++ if u_64 != u64::MAX {
++ u_64 += 1;
++ }
++
++ if u_64 < u64::MAX {
++ u_64 += 1;
++ }
++
++ if u64::MAX > u_64 {
++ u_64 += 1;
++ }
++
++ if i_8 != i8::MAX {
++ i_8 += 1;
++ }
++
++ if i_8 < i8::MAX {
++ i_8 += 1;
++ }
++
++ if i8::MAX > i_8 {
++ i_8 += 1;
++ }
++
++ if i_16 != i16::MAX {
++ i_16 += 1;
++ }
++
++ if i_16 < i16::MAX {
++ i_16 += 1;
++ }
++
++ if i16::MAX > i_16 {
++ i_16 += 1;
++ }
++
++ if i_32 != i32::MAX {
++ i_32 += 1;
++ }
++
++ if i_32 < i32::MAX {
++ i_32 += 1;
++ }
++
++ if i32::MAX > i_32 {
++ i_32 += 1;
++ }
++
++ if i_64 != i64::MAX {
++ i_64 += 1;
++ }
++
++ if i_64 < i64::MAX {
++ i_64 += 1;
++ }
++
++ if i64::MAX > i_64 {
++ i_64 += 1;
++ }
++
++ if i_64 < 42 {
++ i_64 += 1;
++ }
++
++ if 42 > i_64 {
++ i_64 += 1;
++ }
++
++ let a = 12;
++ let mut b = 8;
++
++ if a < u8::MAX {
++ b += 1;
++ }
++
++ if u8::MAX > a {
++ b += 1;
++ }
++
++ if u_32 < u32::MAX {
++ u_32 += 1;
++ } else {
++ println!("don't lint this");
++ }
++
++ if u_32 < u32::MAX {
++ println!("don't lint this");
++ u_32 += 1;
++ }
++
++ if u_32 < 42 {
++ println!("brace yourself!");
++ } else if u_32 < u32::MAX {
++ u_32 += 1;
++ }
++}
--- /dev/null
--- /dev/null
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:23:5
++ |
++LL | / if u_8 != u8::MAX {
++LL | | u_8 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_8 = u_8.saturating_add(1);`
++ |
++ = note: `-D clippy::implicit-saturating-add` implied by `-D warnings`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:27:5
++ |
++LL | / if u_8 < u8::MAX {
++LL | | u_8 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_8 = u_8.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:35:5
++ |
++LL | / if u_16 != u16::MAX {
++LL | | u_16 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_16 = u_16.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:39:5
++ |
++LL | / if u_16 < u16::MAX {
++LL | | u_16 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_16 = u_16.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:43:5
++ |
++LL | / if u16::MAX > u_16 {
++LL | | u_16 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_16 = u_16.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:47:5
++ |
++LL | / if u_32 != u32::MAX {
++LL | | u_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_32 = u_32.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:51:5
++ |
++LL | / if u_32 < u32::MAX {
++LL | | u_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_32 = u_32.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:55:5
++ |
++LL | / if u32::MAX > u_32 {
++LL | | u_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_32 = u_32.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:59:5
++ |
++LL | / if u_64 != u64::MAX {
++LL | | u_64 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_64 = u_64.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:63:5
++ |
++LL | / if u_64 < u64::MAX {
++LL | | u_64 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_64 = u_64.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:67:5
++ |
++LL | / if u64::MAX > u_64 {
++LL | | u_64 += 1;
++LL | | }
++ | |_____^ help: use instead: `u_64 = u_64.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:71:5
++ |
++LL | / if i_8 != i8::MAX {
++LL | | i_8 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_8 = i_8.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:75:5
++ |
++LL | / if i_8 < i8::MAX {
++LL | | i_8 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_8 = i_8.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:79:5
++ |
++LL | / if i8::MAX > i_8 {
++LL | | i_8 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_8 = i_8.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:83:5
++ |
++LL | / if i_16 != i16::MAX {
++LL | | i_16 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_16 = i_16.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:87:5
++ |
++LL | / if i_16 < i16::MAX {
++LL | | i_16 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_16 = i_16.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:91:5
++ |
++LL | / if i16::MAX > i_16 {
++LL | | i_16 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_16 = i_16.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:95:5
++ |
++LL | / if i_32 != i32::MAX {
++LL | | i_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_32 = i_32.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:99:5
++ |
++LL | / if i_32 < i32::MAX {
++LL | | i_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_32 = i_32.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:103:5
++ |
++LL | / if i32::MAX > i_32 {
++LL | | i_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_32 = i_32.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:107:5
++ |
++LL | / if i_64 != i64::MAX {
++LL | | i_64 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_64 = i_64.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:111:5
++ |
++LL | / if i_64 < i64::MAX {
++LL | | i_64 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_64 = i_64.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:115:5
++ |
++LL | / if i64::MAX > i_64 {
++LL | | i_64 += 1;
++LL | | }
++ | |_____^ help: use instead: `i_64 = i_64.saturating_add(1);`
++
++error: manual saturating add detected
++ --> $DIR/implicit_saturating_add.rs:151:12
++ |
++LL | } else if u_32 < u32::MAX {
++ | ____________^
++LL | | u_32 += 1;
++LL | | }
++ | |_____^ help: use instead: `{u_32 = u_32.saturating_add(1); }`
++
++error: aborting due to 24 previous errors
++
--- /dev/null
+#![deny(clippy::index_refutable_slice)]
++#![allow(clippy::uninlined_format_args)]
+
+enum SomeEnum<T> {
+ One(T),
+ Two(T),
+ Three(T),
+ Four(T),
+}
+
+fn lintable_examples() {
+ // Try with reference
+ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ if let Some(slice) = slice {
+ println!("{}", slice[0]);
+ }
+
+ // Try with copy
+ let slice: Option<[u32; 3]> = Some([1, 2, 3]);
+ if let Some(slice) = slice {
+ println!("{}", slice[0]);
+ }
+
+ // Try with long slice and small indices
+ let slice: Option<[u32; 9]> = Some([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ if let Some(slice) = slice {
+ println!("{}", slice[2]);
+ println!("{}", slice[0]);
+ }
+
+ // Multiple bindings
+ let slice_wrapped: SomeEnum<[u32; 3]> = SomeEnum::One([5, 6, 7]);
+ if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped {
+ println!("{}", slice[0]);
+ }
+
+ // Two lintable slices in one if let
+ let a_wrapped: SomeEnum<[u32; 3]> = SomeEnum::One([9, 5, 1]);
+ let b_wrapped: Option<[u32; 2]> = Some([4, 6]);
+ if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
+ println!("{} -> {}", a[2], b[1]);
+ }
+
+ // This requires the slice values to be borrowed as the slice values can only be
+ // borrowed and `String` doesn't implement copy
+ let slice: Option<[String; 2]> = Some([String::from("1"), String::from("2")]);
+ if let Some(ref slice) = slice {
+ println!("{:?}", slice[1]);
+ }
+ println!("{:?}", slice);
+
+ // This should not suggest using the `ref` keyword as the scrutinee is already
+ // a reference
+ let slice: Option<[String; 2]> = Some([String::from("1"), String::from("2")]);
+ if let Some(slice) = &slice {
+ println!("{:?}", slice[0]);
+ }
+ println!("{:?}", slice);
+}
+
+fn slice_index_above_limit() {
+ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+
+ if let Some(slice) = slice {
+ // Would cause a panic, IDK
+ println!("{}", slice[7]);
+ }
+}
+
+fn slice_is_used() {
+ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ if let Some(slice) = slice {
+ println!("{:?}", slice.len());
+ }
+
+ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ if let Some(slice) = slice {
+ println!("{:?}", slice.to_vec());
+ }
+
+ let opt: Option<[String; 2]> = Some([String::from("Hello"), String::from("world")]);
+ if let Some(slice) = opt {
+ if !slice.is_empty() {
+ println!("first: {}", slice[0]);
+ }
+ }
+}
+
+/// The slice is used by an external function and should therefore not be linted
+fn check_slice_as_arg() {
+ fn is_interesting<T>(slice: &[T; 2]) -> bool {
+ !slice.is_empty()
+ }
+
+ let slice_wrapped: Option<[String; 2]> = Some([String::from("Hello"), String::from("world")]);
+ if let Some(slice) = &slice_wrapped {
+ if is_interesting(slice) {
+ println!("This is interesting {}", slice[0]);
+ }
+ }
+ println!("{:?}", slice_wrapped);
+}
+
+fn check_slice_in_struct() {
+ #[derive(Debug)]
+ struct Wrapper<'a> {
+ inner: Option<&'a [String]>,
+ is_awesome: bool,
+ }
+
+ impl<'a> Wrapper<'a> {
+ fn is_super_awesome(&self) -> bool {
+ self.is_awesome
+ }
+ }
+
+ let inner = &[String::from("New"), String::from("World")];
+ let wrap = Wrapper {
+ inner: Some(inner),
+ is_awesome: true,
+ };
+
+ // Test 1: Field access
+ if let Some(slice) = wrap.inner {
+ if wrap.is_awesome {
+ println!("This is awesome! {}", slice[0]);
+ }
+ }
+
+ // Test 2: function access
+ if let Some(slice) = wrap.inner {
+ if wrap.is_super_awesome() {
+ println!("This is super awesome! {}", slice[0]);
+ }
+ }
+ println!("Complete wrap: {:?}", wrap);
+}
+
+/// This would be a nice additional feature to have in the future, but adding it
+/// now would make the PR too large. This is therefore only a test that we don't
+/// lint cases we can't make a reasonable suggestion for
+fn mutable_slice_index() {
+ // Mut access
+ let mut slice: Option<[String; 1]> = Some([String::from("Penguin")]);
+ if let Some(ref mut slice) = slice {
+ slice[0] = String::from("Mr. Penguin");
+ }
+ println!("Use after modification: {:?}", slice);
+
+ // Mut access on reference
+ let mut slice: Option<[String; 1]> = Some([String::from("Cat")]);
+ if let Some(slice) = &mut slice {
+ slice[0] = String::from("Lord Meow Meow");
+ }
+ println!("Use after modification: {:?}", slice);
+}
+
+/// The lint will ignore bindings with sub patterns as it would be hard
+/// to build correct suggestions for these instances :)
+fn binding_with_sub_pattern() {
+ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
+ if let Some(slice @ [_, _, _]) = slice {
+ println!("{:?}", slice[2]);
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/if_let_slice_binding.rs:13:17
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:19:17
++ --> $DIR/if_let_slice_binding.rs:14:17
+ |
+LL | if let Some(slice) = slice {
+ | ^^^^^
+ |
+note: the lint level is defined here
+ --> $DIR/if_let_slice_binding.rs:1:9
+ |
+LL | #![deny(clippy::index_refutable_slice)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: try using a slice pattern here
+ |
+LL | if let Some([slice_0, ..]) = slice {
+ | ~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{}", slice_0);
+ | ~~~~~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:25:17
++ --> $DIR/if_let_slice_binding.rs:20:17
+ |
+LL | if let Some(slice) = slice {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let Some([slice_0, ..]) = slice {
+ | ~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{}", slice_0);
+ | ~~~~~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:32:26
++ --> $DIR/if_let_slice_binding.rs:26:17
+ |
+LL | if let Some(slice) = slice {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let Some([slice_0, _, slice_2, ..]) = slice {
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL ~ println!("{}", slice_2);
+LL ~ println!("{}", slice_0);
+ |
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:39:29
++ --> $DIR/if_let_slice_binding.rs:33:26
+ |
+LL | if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let SomeEnum::One([slice_0, ..]) | SomeEnum::Three([slice_0, ..]) = slice_wrapped {
+ | ~~~~~~~~~~~~~ ~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{}", slice_0);
+ | ~~~~~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:39:38
++ --> $DIR/if_let_slice_binding.rs:40:29
+ |
+LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
+ | ^
+ |
+help: try using a slice pattern here
+ |
+LL | if let (SomeEnum::Three([_, _, a_2, ..]), Some(b)) = (a_wrapped, b_wrapped) {
+ | ~~~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{} -> {}", a_2, b[1]);
+ | ~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:46:21
++ --> $DIR/if_let_slice_binding.rs:40:38
+ |
+LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
+ | ^
+ |
+help: try using a slice pattern here
+ |
+LL | if let (SomeEnum::Three(a), Some([_, b_1, ..])) = (a_wrapped, b_wrapped) {
+ | ~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{} -> {}", a[2], b_1);
+ | ~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:54:17
++ --> $DIR/if_let_slice_binding.rs:47:21
+ |
+LL | if let Some(ref slice) = slice {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let Some([_, ref slice_1, ..]) = slice {
+ | ~~~~~~~~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{:?}", slice_1);
+ | ~~~~~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:123:17
++ --> $DIR/if_let_slice_binding.rs:55:17
+ |
+LL | if let Some(slice) = &slice {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let Some([slice_0, ..]) = &slice {
+ | ~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("{:?}", slice_0);
+ | ~~~~~~~
+
+error: this binding can be a slice pattern to avoid indexing
- --> $DIR/if_let_slice_binding.rs:130:17
++ --> $DIR/if_let_slice_binding.rs:124:17
+ |
+LL | if let Some(slice) = wrap.inner {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let Some([slice_0, ..]) = wrap.inner {
+ | ~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("This is awesome! {}", slice_0);
+ | ~~~~~~~
+
+error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:131:17
+ |
+LL | if let Some(slice) = wrap.inner {
+ | ^^^^^
+ |
+help: try using a slice pattern here
+ |
+LL | if let Some([slice_0, ..]) = wrap.inner {
+ | ~~~~~~~~~~~~~
+help: and replace the index expressions here
+ |
+LL | println!("This is super awesome! {}", slice_0);
+ | ~~~~~~~
+
+error: aborting due to 10 previous errors
+
--- /dev/null
++#![allow(clippy::uninlined_format_args)]
++
+use std::iter::repeat;
+fn square_is_lower_64(x: &u32) -> bool {
+ x * x < 64
+}
+
+#[allow(clippy::maybe_infinite_iter)]
+#[deny(clippy::infinite_iter)]
+fn infinite_iters() {
+ repeat(0_u8).collect::<Vec<_>>(); // infinite iter
+ (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter
+ (0..8_u64).chain(0..).max(); // infinite iter
+ (0_usize..)
+ .chain([0usize, 1, 2].iter().cloned())
+ .skip_while(|x| *x != 42)
+ .min(); // infinite iter
+ (0..8_u32)
+ .rev()
+ .cycle()
+ .map(|x| x + 1_u32)
+ .for_each(|x| println!("{}", x)); // infinite iter
+ (0..3_u32).flat_map(|x| x..).sum::<u32>(); // infinite iter
+ (0_usize..).flat_map(|x| 0..x).product::<usize>(); // infinite iter
+ (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter
+ (0..42_u64).by_ref().last(); // not an infinite, because ranges are double-ended
+ (0..).next(); // iterator is not exhausted
+}
+
+#[deny(clippy::maybe_infinite_iter)]
+fn potential_infinite_iters() {
+ (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter
+ repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter
+ (1..)
+ .scan(0, |state, x| {
+ *state += x;
+ Some(*state)
+ })
+ .min(); // maybe infinite iter
+ (0..).find(|x| *x == 24); // maybe infinite iter
+ (0..).position(|x| x == 24); // maybe infinite iter
+ (0..).any(|x| x == 24); // maybe infinite iter
+ (0..).all(|x| x == 24); // maybe infinite iter
+
+ (0..).zip(0..42).take_while(|&(x, _)| x != 42).count(); // not infinite
+ repeat(42).take_while(|x| *x == 42).next(); // iterator is not exhausted
+}
+
+fn main() {
+ infinite_iters();
+ potential_infinite_iters();
+}
+
+mod finite_collect {
+ use std::collections::HashSet;
+
+ struct C;
+ impl FromIterator<i32> for C {
+ fn from_iter<I: IntoIterator<Item = i32>>(iter: I) -> Self {
+ C
+ }
+ }
+
+ fn check_collect() {
+ let _: HashSet<i32> = (0..).collect(); // Infinite iter
+
+ // Some data structures don't collect infinitely, such as `ArrayVec`
+ let _: C = (0..).collect();
+ }
+}
--- /dev/null
- --> $DIR/infinite_iter.rs:9:5
+error: infinite iteration detected
- --> $DIR/infinite_iter.rs:7:8
++ --> $DIR/infinite_iter.rs:11:5
+ |
+LL | repeat(0_u8).collect::<Vec<_>>(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the lint level is defined here
- --> $DIR/infinite_iter.rs:10:5
++ --> $DIR/infinite_iter.rs:9:8
+ |
+LL | #[deny(clippy::infinite_iter)]
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
- --> $DIR/infinite_iter.rs:11:5
++ --> $DIR/infinite_iter.rs:12:5
+ |
+LL | (0..8_u32).take_while(square_is_lower_64).cycle().count(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
- --> $DIR/infinite_iter.rs:16:5
++ --> $DIR/infinite_iter.rs:13:5
+ |
+LL | (0..8_u64).chain(0..).max(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
- --> $DIR/infinite_iter.rs:22:5
++ --> $DIR/infinite_iter.rs:18:5
+ |
+LL | / (0..8_u32)
+LL | | .rev()
+LL | | .cycle()
+LL | | .map(|x| x + 1_u32)
+LL | | .for_each(|x| println!("{}", x)); // infinite iter
+ | |________________________________________^
+
+error: infinite iteration detected
- --> $DIR/infinite_iter.rs:23:5
++ --> $DIR/infinite_iter.rs:24:5
+ |
+LL | (0_usize..).flat_map(|x| 0..x).product::<usize>(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
- --> $DIR/infinite_iter.rs:30:5
++ --> $DIR/infinite_iter.rs:25:5
+ |
+LL | (0_u64..).filter(|x| x % 2 == 0).last(); // infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:28:8
++ --> $DIR/infinite_iter.rs:32:5
+ |
+LL | (0..).zip((0..).take_while(square_is_lower_64)).count(); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the lint level is defined here
- --> $DIR/infinite_iter.rs:31:5
++ --> $DIR/infinite_iter.rs:30:8
+ |
+LL | #[deny(clippy::maybe_infinite_iter)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:32:5
++ --> $DIR/infinite_iter.rs:33:5
+ |
+LL | repeat(42).take_while(|x| *x == 42).chain(0..42).max(); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:38:5
++ --> $DIR/infinite_iter.rs:34:5
+ |
+LL | / (1..)
+LL | | .scan(0, |state, x| {
+LL | | *state += x;
+LL | | Some(*state)
+LL | | })
+LL | | .min(); // maybe infinite iter
+ | |______________^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:39:5
++ --> $DIR/infinite_iter.rs:40:5
+ |
+LL | (0..).find(|x| *x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:40:5
++ --> $DIR/infinite_iter.rs:41:5
+ |
+LL | (0..).position(|x| x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:41:5
++ --> $DIR/infinite_iter.rs:42:5
+ |
+LL | (0..).any(|x| x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: possible infinite iteration detected
- --> $DIR/infinite_iter.rs:63:31
++ --> $DIR/infinite_iter.rs:43:5
+ |
+LL | (0..).all(|x| x == 24); // maybe infinite iter
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: infinite iteration detected
++ --> $DIR/infinite_iter.rs:65:31
+ |
+LL | let _: HashSet<i32> = (0..).collect(); // Infinite iter
+ | ^^^^^^^^^^^^^^^
+ |
+ = note: `#[deny(clippy::infinite_iter)]` on by default
+
+error: aborting due to 14 previous errors
+
--- /dev/null
+// run-rustfix
+#![deny(clippy::while_let_on_iterator)]
+#![allow(unused_mut)]
++#![allow(clippy::uninlined_format_args)]
+
+use std::iter::Iterator;
+
+struct Foo;
+
+impl Foo {
+ fn foo1<I: Iterator<Item = usize>>(mut it: I) {
+ while let Some(_) = it.next() {
+ println!("{:?}", it.size_hint());
+ }
+ }
+
+ fn foo2<I: Iterator<Item = usize>>(mut it: I) {
+ for e in it {
+ println!("{:?}", e);
+ }
+ }
+}
+
+fn main() {
+ Foo::foo1(vec![].into_iter());
+ Foo::foo2(vec![].into_iter());
+}
--- /dev/null
+// run-rustfix
+#![deny(clippy::while_let_on_iterator)]
+#![allow(unused_mut)]
++#![allow(clippy::uninlined_format_args)]
+
+use std::iter::Iterator;
+
+struct Foo;
+
+impl Foo {
+ fn foo1<I: Iterator<Item = usize>>(mut it: I) {
+ while let Some(_) = it.next() {
+ println!("{:?}", it.size_hint());
+ }
+ }
+
+ fn foo2<I: Iterator<Item = usize>>(mut it: I) {
+ while let Some(e) = it.next() {
+ println!("{:?}", e);
+ }
+ }
+}
+
+fn main() {
+ Foo::foo1(vec![].into_iter());
+ Foo::foo2(vec![].into_iter());
+}
--- /dev/null
- --> $DIR/issue_2356.rs:17:9
+error: this loop could be written as a `for` loop
++ --> $DIR/issue_2356.rs:18:9
+ |
+LL | while let Some(e) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for e in it`
+ |
+note: the lint level is defined here
+ --> $DIR/issue_2356.rs:2:9
+ |
+LL | #![deny(clippy::while_let_on_iterator)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
--- /dev/null
+#![allow(dead_code)]
++#![allow(clippy::uninlined_format_args)]
+
+async fn sink1<'a>(_: &'a str) {} // lint
+async fn sink1_elided(_: &str) {} // ok
+
+// lint
+async fn one_to_one<'a>(s: &'a str) -> &'a str {
+ s
+}
+
+// ok
+async fn one_to_one_elided(s: &str) -> &str {
+ s
+}
+
+// ok
+async fn all_to_one<'a>(a: &'a str, _b: &'a str) -> &'a str {
+ a
+}
+
+// async fn unrelated(_: &str, _: &str) {} // Not allowed in async fn
+
+// #3988
+struct Foo;
+impl Foo {
+ // ok
+ pub async fn new(&mut self) -> Self {
+ Foo {}
+ }
+}
+
+// rust-lang/rust#61115
+// ok
+async fn print(s: &str) {
+ println!("{}", s);
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/issue_4266.rs:3:1
+error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
- --> $DIR/issue_4266.rs:7:1
++ --> $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)
- --> $DIR/issue_4266.rs:27:22
++ --> $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
+#![warn(clippy::items_after_statements)]
++#![allow(clippy::uninlined_format_args)]
+
+fn ok() {
+ fn foo() {
+ println!("foo");
+ }
+ foo();
+}
+
+fn last() {
+ foo();
+ fn foo() {
+ println!("foo");
+ }
+}
+
+fn main() {
+ foo();
+ fn foo() {
+ println!("foo");
+ }
+ foo();
+}
+
+fn mac() {
+ let mut a = 5;
+ println!("{}", a);
+ // do not lint this, because it needs to be after `a`
+ macro_rules! b {
+ () => {{
+ a = 6;
+ fn say_something() {
+ println!("something");
+ }
+ }};
+ }
+ b!();
+ println!("{}", a);
+}
+
+fn semicolon() {
+ struct S {
+ a: u32,
+ };
+ impl S {
+ fn new(a: u32) -> Self {
+ Self { a }
+ }
+ }
+
+ let _ = S::new(3);
+}
--- /dev/null
- --> $DIR/item_after_statement.rs:12:5
+error: adding items after statements is confusing, since items exist from the start of the scope
- --> $DIR/item_after_statement.rs:19:5
++ --> $DIR/item_after_statement.rs:13:5
+ |
+LL | / fn foo() {
+LL | | println!("foo");
+LL | | }
+ | |_____^
+ |
+ = note: `-D clippy::items-after-statements` implied by `-D warnings`
+
+error: adding items after statements is confusing, since items exist from the start of the scope
- --> $DIR/item_after_statement.rs:32:13
++ --> $DIR/item_after_statement.rs:20:5
+ |
+LL | / fn foo() {
+LL | | println!("foo");
+LL | | }
+ | |_____^
+
+error: adding items after statements is confusing, since items exist from the start of the scope
++ --> $DIR/item_after_statement.rs:33:13
+ |
+LL | / fn say_something() {
+LL | | println!("something");
+LL | | }
+ | |_____________^
+...
+LL | b!();
+ | ---- in this macro invocation
+ |
+ = note: this error originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- #![allow(clippy::nonminimal_bool)]
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
+
+#![warn(clippy::manual_assert)]
++#![allow(dead_code, unused_doc_comments)]
++#![allow(clippy::nonminimal_bool, clippy::uninlined_format_args)]
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c.is_some()
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ assert!(a.is_empty(), "qaqaq{:?}", a);
+ assert!(a.is_empty(), "qwqwq");
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ assert!(!b.is_empty(), "panic1");
+ assert!(!(b.is_empty() && a.is_empty()), "panic2");
+ assert!(!(a.is_empty() && !b.is_empty()), "panic3");
+ assert!(!(b.is_empty() || a.is_empty()), "panic4");
+ assert!(!(a.is_empty() || !b.is_empty()), "panic5");
+ assert!(!a.is_empty(), "with expansion {}", one!());
+}
++
++fn issue7730(a: u8) {
++ // Suggestion should preserve comment
++ // comment
++/* this is a
++ multiline
++ comment */
++/// Doc comment
++// comment after `panic!`
++assert!(!(a > 2), "panic with comment");
++}
--- /dev/null
- --> $DIR/manual_assert.rs:30:5
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(a.is_empty(), "qaqaq{:?}", a);`
++ --> $DIR/manual_assert.rs:31:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qaqaq{:?}", a);
+LL | | }
- --> $DIR/manual_assert.rs:33:5
++ | |_____^
+ |
+ = note: `-D clippy::manual-assert` implied by `-D warnings`
++help: try instead
++ |
++LL | assert!(a.is_empty(), "qaqaq{:?}", a);
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");`
++ --> $DIR/manual_assert.rs:34:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qwqwq");
+LL | | }
- --> $DIR/manual_assert.rs:50:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(a.is_empty(), "qwqwq");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!b.is_empty(), "panic1");`
++ --> $DIR/manual_assert.rs:51:5
+ |
+LL | / if b.is_empty() {
+LL | | panic!("panic1");
+LL | | }
- --> $DIR/manual_assert.rs:53:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!b.is_empty(), "panic1");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");`
++ --> $DIR/manual_assert.rs:54:5
+ |
+LL | / if b.is_empty() && a.is_empty() {
+LL | | panic!("panic2");
+LL | | }
- --> $DIR/manual_assert.rs:56:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(b.is_empty() && a.is_empty()), "panic2");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");`
++ --> $DIR/manual_assert.rs:57:5
+ |
+LL | / if a.is_empty() && !b.is_empty() {
+LL | | panic!("panic3");
+LL | | }
- --> $DIR/manual_assert.rs:59:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(a.is_empty() && !b.is_empty()), "panic3");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");`
++ --> $DIR/manual_assert.rs:60:5
+ |
+LL | / if b.is_empty() || a.is_empty() {
+LL | | panic!("panic4");
+LL | | }
- --> $DIR/manual_assert.rs:62:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(b.is_empty() || a.is_empty()), "panic4");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");`
++ --> $DIR/manual_assert.rs:63:5
+ |
+LL | / if a.is_empty() || !b.is_empty() {
+LL | | panic!("panic5");
+LL | | }
- --> $DIR/manual_assert.rs:65:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(a.is_empty() || !b.is_empty()), "panic5");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!a.is_empty(), "with expansion {}", one!());`
++ --> $DIR/manual_assert.rs:66:5
+ |
+LL | / if a.is_empty() {
+LL | | panic!("with expansion {}", one!())
+LL | | }
- error: aborting due to 8 previous errors
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!a.is_empty(), "with expansion {}", one!());
++ |
++
++error: only a `panic!` in `if`-then statement
++ --> $DIR/manual_assert.rs:73:5
++ |
++LL | / if a > 2 {
++LL | | // comment
++LL | | /* this is a
++LL | | multiline
++... |
++LL | | panic!("panic with comment") // comment after `panic!`
++LL | | }
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(a > 2), "panic with comment");
++ |
+
++error: aborting due to 9 previous errors
+
--- /dev/null
- #![allow(clippy::nonminimal_bool)]
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
+
+#![warn(clippy::manual_assert)]
++#![allow(dead_code, unused_doc_comments)]
++#![allow(clippy::nonminimal_bool, clippy::uninlined_format_args)]
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c.is_some()
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ assert!(a.is_empty(), "qaqaq{:?}", a);
+ assert!(a.is_empty(), "qwqwq");
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ assert!(!b.is_empty(), "panic1");
+ assert!(!(b.is_empty() && a.is_empty()), "panic2");
+ assert!(!(a.is_empty() && !b.is_empty()), "panic3");
+ assert!(!(b.is_empty() || a.is_empty()), "panic4");
+ assert!(!(a.is_empty() || !b.is_empty()), "panic5");
+ assert!(!a.is_empty(), "with expansion {}", one!());
+}
++
++fn issue7730(a: u8) {
++ // Suggestion should preserve comment
++ // comment
++/* this is a
++ multiline
++ comment */
++/// Doc comment
++// comment after `panic!`
++assert!(!(a > 2), "panic with comment");
++}
--- /dev/null
- --> $DIR/manual_assert.rs:30:5
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(a.is_empty(), "qaqaq{:?}", a);`
++ --> $DIR/manual_assert.rs:31:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qaqaq{:?}", a);
+LL | | }
- --> $DIR/manual_assert.rs:33:5
++ | |_____^
+ |
+ = note: `-D clippy::manual-assert` implied by `-D warnings`
++help: try instead
++ |
++LL | assert!(a.is_empty(), "qaqaq{:?}", a);
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");`
++ --> $DIR/manual_assert.rs:34:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qwqwq");
+LL | | }
- --> $DIR/manual_assert.rs:50:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(a.is_empty(), "qwqwq");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!b.is_empty(), "panic1");`
++ --> $DIR/manual_assert.rs:51:5
+ |
+LL | / if b.is_empty() {
+LL | | panic!("panic1");
+LL | | }
- --> $DIR/manual_assert.rs:53:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!b.is_empty(), "panic1");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");`
++ --> $DIR/manual_assert.rs:54:5
+ |
+LL | / if b.is_empty() && a.is_empty() {
+LL | | panic!("panic2");
+LL | | }
- --> $DIR/manual_assert.rs:56:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(b.is_empty() && a.is_empty()), "panic2");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");`
++ --> $DIR/manual_assert.rs:57:5
+ |
+LL | / if a.is_empty() && !b.is_empty() {
+LL | | panic!("panic3");
+LL | | }
- --> $DIR/manual_assert.rs:59:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(a.is_empty() && !b.is_empty()), "panic3");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");`
++ --> $DIR/manual_assert.rs:60:5
+ |
+LL | / if b.is_empty() || a.is_empty() {
+LL | | panic!("panic4");
+LL | | }
- --> $DIR/manual_assert.rs:62:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(b.is_empty() || a.is_empty()), "panic4");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");`
++ --> $DIR/manual_assert.rs:63:5
+ |
+LL | / if a.is_empty() || !b.is_empty() {
+LL | | panic!("panic5");
+LL | | }
- --> $DIR/manual_assert.rs:65:5
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(a.is_empty() || !b.is_empty()), "panic5");
++ |
+
+error: only a `panic!` in `if`-then statement
- | |_____^ help: try: `assert!(!a.is_empty(), "with expansion {}", one!());`
++ --> $DIR/manual_assert.rs:66:5
+ |
+LL | / if a.is_empty() {
+LL | | panic!("with expansion {}", one!())
+LL | | }
- error: aborting due to 8 previous errors
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!a.is_empty(), "with expansion {}", one!());
++ |
++
++error: only a `panic!` in `if`-then statement
++ --> $DIR/manual_assert.rs:73:5
++ |
++LL | / if a > 2 {
++LL | | // comment
++LL | | /* this is a
++LL | | multiline
++... |
++LL | | panic!("panic with comment") // comment after `panic!`
++LL | | }
++ | |_____^
++ |
++help: try instead
++ |
++LL | assert!(!(a > 2), "panic with comment");
++ |
+
++error: aborting due to 9 previous errors
+
--- /dev/null
- #![allow(clippy::nonminimal_bool)]
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
+
+#![warn(clippy::manual_assert)]
++#![allow(dead_code, unused_doc_comments)]
++#![allow(clippy::nonminimal_bool, clippy::uninlined_format_args)]
+
+macro_rules! one {
+ () => {
+ 1
+ };
+}
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c.is_some()
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ if !a.is_empty() {
+ panic!("qaqaq{:?}", a);
+ }
+ if !a.is_empty() {
+ panic!("qwqwq");
+ }
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ if b.is_empty() {
+ panic!("panic1");
+ }
+ if b.is_empty() && a.is_empty() {
+ panic!("panic2");
+ }
+ if a.is_empty() && !b.is_empty() {
+ panic!("panic3");
+ }
+ if b.is_empty() || a.is_empty() {
+ panic!("panic4");
+ }
+ if a.is_empty() || !b.is_empty() {
+ panic!("panic5");
+ }
+ if a.is_empty() {
+ panic!("with expansion {}", one!())
+ }
+}
++
++fn issue7730(a: u8) {
++ // Suggestion should preserve comment
++ if a > 2 {
++ // comment
++ /* this is a
++ multiline
++ comment */
++ /// Doc comment
++ panic!("panic with comment") // comment after `panic!`
++ }
++}
--- /dev/null
- clippy::unnecessary_operation
+// run-rustfix
+
+#![warn(clippy::manual_bits)]
+#![allow(
+ clippy::no_effect,
+ clippy::useless_conversion,
+ path_statements,
+ unused_must_use,
++ clippy::unnecessary_operation,
++ clippy::unnecessary_cast
+)]
+
+use std::mem::{size_of, size_of_val};
+
+fn main() {
+ i8::BITS as usize;
+ i16::BITS as usize;
+ i32::BITS as usize;
+ i64::BITS as usize;
+ i128::BITS as usize;
+ isize::BITS as usize;
+
+ u8::BITS as usize;
+ u16::BITS as usize;
+ u32::BITS as usize;
+ u64::BITS as usize;
+ u128::BITS as usize;
+ usize::BITS as usize;
+
+ i8::BITS as usize;
+ i16::BITS as usize;
+ i32::BITS as usize;
+ i64::BITS as usize;
+ i128::BITS as usize;
+ isize::BITS as usize;
+
+ u8::BITS as usize;
+ u16::BITS as usize;
+ u32::BITS as usize;
+ u64::BITS as usize;
+ u128::BITS as usize;
+ usize::BITS as usize;
+
+ size_of::<usize>() * 4;
+ 4 * size_of::<usize>();
+ size_of::<bool>() * 8;
+ 8 * size_of::<bool>();
+
+ size_of_val(&0u32) * 8;
+
+ type Word = u32;
+ Word::BITS as usize;
+ type Bool = bool;
+ size_of::<Bool>() * 8;
+
+ let _: u32 = u128::BITS as u32;
+ let _: u32 = u128::BITS.try_into().unwrap();
+ let _ = (u128::BITS as usize).pow(5);
+ let _ = &(u128::BITS as usize);
+}
--- /dev/null
- clippy::unnecessary_operation
+// run-rustfix
+
+#![warn(clippy::manual_bits)]
+#![allow(
+ clippy::no_effect,
+ clippy::useless_conversion,
+ path_statements,
+ unused_must_use,
++ clippy::unnecessary_operation,
++ clippy::unnecessary_cast
+)]
+
+use std::mem::{size_of, size_of_val};
+
+fn main() {
+ size_of::<i8>() * 8;
+ size_of::<i16>() * 8;
+ size_of::<i32>() * 8;
+ size_of::<i64>() * 8;
+ size_of::<i128>() * 8;
+ size_of::<isize>() * 8;
+
+ size_of::<u8>() * 8;
+ size_of::<u16>() * 8;
+ size_of::<u32>() * 8;
+ size_of::<u64>() * 8;
+ size_of::<u128>() * 8;
+ size_of::<usize>() * 8;
+
+ 8 * size_of::<i8>();
+ 8 * size_of::<i16>();
+ 8 * size_of::<i32>();
+ 8 * size_of::<i64>();
+ 8 * size_of::<i128>();
+ 8 * size_of::<isize>();
+
+ 8 * size_of::<u8>();
+ 8 * size_of::<u16>();
+ 8 * size_of::<u32>();
+ 8 * size_of::<u64>();
+ 8 * size_of::<u128>();
+ 8 * size_of::<usize>();
+
+ size_of::<usize>() * 4;
+ 4 * size_of::<usize>();
+ size_of::<bool>() * 8;
+ 8 * size_of::<bool>();
+
+ size_of_val(&0u32) * 8;
+
+ type Word = u32;
+ size_of::<Word>() * 8;
+ type Bool = bool;
+ size_of::<Bool>() * 8;
+
+ let _: u32 = (size_of::<u128>() * 8) as u32;
+ let _: u32 = (size_of::<u128>() * 8).try_into().unwrap();
+ let _ = (size_of::<u128>() * 8).pow(5);
+ let _ = &(size_of::<u128>() * 8);
+}
--- /dev/null
- --> $DIR/manual_bits.rs:15:5
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:16:5
++ --> $DIR/manual_bits.rs:16:5
+ |
+LL | size_of::<i8>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^ help: consider using: `i8::BITS as usize`
+ |
+ = note: `-D clippy::manual-bits` implied by `-D warnings`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:17:5
++ --> $DIR/manual_bits.rs:17:5
+ |
+LL | size_of::<i16>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `i16::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:18:5
++ --> $DIR/manual_bits.rs:18:5
+ |
+LL | size_of::<i32>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `i32::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:19:5
++ --> $DIR/manual_bits.rs:19:5
+ |
+LL | size_of::<i64>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `i64::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:20:5
++ --> $DIR/manual_bits.rs:20:5
+ |
+LL | size_of::<i128>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `i128::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:22:5
++ --> $DIR/manual_bits.rs:21:5
+ |
+LL | size_of::<isize>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `isize::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:23:5
++ --> $DIR/manual_bits.rs:23:5
+ |
+LL | size_of::<u8>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^ help: consider using: `u8::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:24:5
++ --> $DIR/manual_bits.rs:24:5
+ |
+LL | size_of::<u16>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `u16::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:25:5
++ --> $DIR/manual_bits.rs:25:5
+ |
+LL | size_of::<u32>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `u32::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:26:5
++ --> $DIR/manual_bits.rs:26:5
+ |
+LL | size_of::<u64>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `u64::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:27:5
++ --> $DIR/manual_bits.rs:27:5
+ |
+LL | size_of::<u128>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `u128::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:29:5
++ --> $DIR/manual_bits.rs:28:5
+ |
+LL | size_of::<usize>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `usize::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:30:5
++ --> $DIR/manual_bits.rs:30:5
+ |
+LL | 8 * size_of::<i8>();
+ | ^^^^^^^^^^^^^^^^^^^ help: consider using: `i8::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:31:5
++ --> $DIR/manual_bits.rs:31:5
+ |
+LL | 8 * size_of::<i16>();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `i16::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:32:5
++ --> $DIR/manual_bits.rs:32:5
+ |
+LL | 8 * size_of::<i32>();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `i32::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:33:5
++ --> $DIR/manual_bits.rs:33:5
+ |
+LL | 8 * size_of::<i64>();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `i64::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:34:5
++ --> $DIR/manual_bits.rs:34:5
+ |
+LL | 8 * size_of::<i128>();
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `i128::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:36:5
++ --> $DIR/manual_bits.rs:35:5
+ |
+LL | 8 * size_of::<isize>();
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `isize::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:37:5
++ --> $DIR/manual_bits.rs:37:5
+ |
+LL | 8 * size_of::<u8>();
+ | ^^^^^^^^^^^^^^^^^^^ help: consider using: `u8::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:38:5
++ --> $DIR/manual_bits.rs:38:5
+ |
+LL | 8 * size_of::<u16>();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `u16::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:39:5
++ --> $DIR/manual_bits.rs:39:5
+ |
+LL | 8 * size_of::<u32>();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `u32::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:40:5
++ --> $DIR/manual_bits.rs:40:5
+ |
+LL | 8 * size_of::<u64>();
+ | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `u64::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:41:5
++ --> $DIR/manual_bits.rs:41:5
+ |
+LL | 8 * size_of::<u128>();
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `u128::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:51:5
++ --> $DIR/manual_bits.rs:42:5
+ |
+LL | 8 * size_of::<usize>();
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `usize::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:55:18
++ --> $DIR/manual_bits.rs:52:5
+ |
+LL | size_of::<Word>() * 8;
+ | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `Word::BITS as usize`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:56:18
++ --> $DIR/manual_bits.rs:56:18
+ |
+LL | let _: u32 = (size_of::<u128>() * 8) as u32;
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `u128::BITS`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:57:13
++ --> $DIR/manual_bits.rs:57:18
+ |
+LL | let _: u32 = (size_of::<u128>() * 8).try_into().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `u128::BITS`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
- --> $DIR/manual_bits.rs:58:14
++ --> $DIR/manual_bits.rs:58:13
+ |
+LL | let _ = (size_of::<u128>() * 8).pow(5);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(u128::BITS as usize)`
+
+error: usage of `mem::size_of::<T>()` to obtain the size of `T` in bits
++ --> $DIR/manual_bits.rs:59:14
+ |
+LL | let _ = &(size_of::<u128>() * 8);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(u128::BITS as usize)`
+
+error: aborting due to 29 previous errors
+
--- /dev/null
--- /dev/null
++#![warn(clippy::manual_clamp)]
++#![allow(
++ unused,
++ dead_code,
++ clippy::unnecessary_operation,
++ clippy::no_effect,
++ clippy::if_same_then_else
++)]
++
++use std::cmp::{max as cmp_max, min as cmp_min};
++
++const CONST_MAX: i32 = 10;
++const CONST_MIN: i32 = 4;
++
++const CONST_F64_MAX: f64 = 10.0;
++const CONST_F64_MIN: f64 = 4.0;
++
++fn main() {
++ let (input, min, max) = (0, -2, 3);
++ // Lint
++ let x0 = if max < input {
++ max
++ } else if min > input {
++ min
++ } else {
++ input
++ };
++
++ let x1 = if input > max {
++ max
++ } else if input < min {
++ min
++ } else {
++ input
++ };
++
++ let x2 = if input < min {
++ min
++ } else if input > max {
++ max
++ } else {
++ input
++ };
++
++ let x3 = if min > input {
++ min
++ } else if max < input {
++ max
++ } else {
++ input
++ };
++
++ let x4 = input.max(min).min(max);
++
++ let x5 = input.min(max).max(min);
++
++ let x6 = match input {
++ x if x > max => max,
++ x if x < min => min,
++ x => x,
++ };
++
++ let x7 = match input {
++ x if x < min => min,
++ x if x > max => max,
++ x => x,
++ };
++
++ let x8 = match input {
++ x if max < x => max,
++ x if min > x => min,
++ x => x,
++ };
++
++ let mut x9 = input;
++ if x9 < min {
++ x9 = min;
++ }
++ if x9 > max {
++ x9 = max;
++ }
++
++ let x10 = match input {
++ x if min > x => min,
++ x if max < x => max,
++ x => x,
++ };
++
++ let mut x11 = input;
++ let _ = 1;
++ if x11 > max {
++ x11 = max;
++ }
++ if x11 < min {
++ x11 = min;
++ }
++
++ let mut x12 = input;
++ if min > x12 {
++ x12 = min;
++ }
++ if max < x12 {
++ x12 = max;
++ }
++
++ let mut x13 = input;
++ if max < x13 {
++ x13 = max;
++ }
++ if min > x13 {
++ x13 = min;
++ }
++
++ let x14 = if input > CONST_MAX {
++ CONST_MAX
++ } else if input < CONST_MIN {
++ CONST_MIN
++ } else {
++ input
++ };
++ {
++ let (input, min, max) = (0.0f64, -2.0, 3.0);
++ let x15 = if input > max {
++ max
++ } else if input < min {
++ min
++ } else {
++ input
++ };
++ }
++ {
++ let input: i32 = cmp_min_max(1);
++ // These can only be detected if exactly one of the arguments to the inner function is const.
++ let x16 = cmp_max(cmp_min(input, CONST_MAX), CONST_MIN);
++ let x17 = cmp_min(cmp_max(input, CONST_MIN), CONST_MAX);
++ let x18 = cmp_max(CONST_MIN, cmp_min(input, CONST_MAX));
++ let x19 = cmp_min(CONST_MAX, cmp_max(input, CONST_MIN));
++ let x20 = cmp_max(cmp_min(CONST_MAX, input), CONST_MIN);
++ let x21 = cmp_min(cmp_max(CONST_MIN, input), CONST_MAX);
++ let x22 = cmp_max(CONST_MIN, cmp_min(CONST_MAX, input));
++ let x23 = cmp_min(CONST_MAX, cmp_max(CONST_MIN, input));
++ let input: f64 = cmp_min_max(1) as f64;
++ let x24 = f64::max(f64::min(input, CONST_F64_MAX), CONST_F64_MIN);
++ let x25 = f64::min(f64::max(input, CONST_F64_MIN), CONST_F64_MAX);
++ let x26 = f64::max(CONST_F64_MIN, f64::min(input, CONST_F64_MAX));
++ let x27 = f64::min(CONST_F64_MAX, f64::max(input, CONST_F64_MIN));
++ let x28 = f64::max(f64::min(CONST_F64_MAX, input), CONST_F64_MIN);
++ let x29 = f64::min(f64::max(CONST_F64_MIN, input), CONST_F64_MAX);
++ let x30 = f64::max(CONST_F64_MIN, f64::min(CONST_F64_MAX, input));
++ let x31 = f64::min(CONST_F64_MAX, f64::max(CONST_F64_MIN, input));
++ }
++ let mut x32 = input;
++ if x32 < min {
++ x32 = min;
++ } else if x32 > max {
++ x32 = max;
++ }
++
++ // It's important this be the last set of statements
++ let mut x33 = input;
++ if max < x33 {
++ x33 = max;
++ }
++ if min > x33 {
++ x33 = min;
++ }
++}
++
++// This code intentionally nonsense.
++fn no_lint() {
++ let (input, min, max) = (0, -2, 3);
++ let x0 = if max < input {
++ max
++ } else if min > input {
++ max
++ } else {
++ min
++ };
++
++ let x1 = if input > max {
++ max
++ } else if input > min {
++ min
++ } else {
++ max
++ };
++
++ let x2 = if max < min {
++ min
++ } else if input > max {
++ input
++ } else {
++ input
++ };
++
++ let x3 = if min > input {
++ input
++ } else if max < input {
++ max
++ } else {
++ max
++ };
++
++ let x6 = match input {
++ x if x < max => x,
++ x if x < min => x,
++ x => x,
++ };
++
++ let x7 = match input {
++ x if x < min => max,
++ x if x > max => min,
++ x => x,
++ };
++
++ let x8 = match input {
++ x if max > x => max,
++ x if min > x => min,
++ x => x,
++ };
++
++ let mut x9 = input;
++ if x9 > min {
++ x9 = min;
++ }
++ if x9 > max {
++ x9 = max;
++ }
++
++ let x10 = match input {
++ x if min > x => min,
++ x if max < x => max,
++ x => min,
++ };
++
++ let mut x11 = input;
++ if x11 > max {
++ x11 = min;
++ }
++ if x11 < min {
++ x11 = max;
++ }
++
++ let mut x12 = input;
++ if min > x12 {
++ x12 = max * 3;
++ }
++ if max < x12 {
++ x12 = min;
++ }
++
++ let mut x13 = input;
++ if max < x13 {
++ let x13 = max;
++ }
++ if min > x13 {
++ x13 = min;
++ }
++ let mut x14 = input;
++ if x14 < min {
++ x14 = 3;
++ } else if x14 > max {
++ x14 = max;
++ }
++ {
++ let input: i32 = cmp_min_max(1);
++ // These can only be detected if exactly one of the arguments to the inner function is const.
++ let x16 = cmp_max(cmp_max(input, CONST_MAX), CONST_MIN);
++ let x17 = cmp_min(cmp_min(input, CONST_MIN), CONST_MAX);
++ let x18 = cmp_max(CONST_MIN, cmp_max(input, CONST_MAX));
++ let x19 = cmp_min(CONST_MAX, cmp_min(input, CONST_MIN));
++ let x20 = cmp_max(cmp_max(CONST_MAX, input), CONST_MIN);
++ let x21 = cmp_min(cmp_min(CONST_MIN, input), CONST_MAX);
++ let x22 = cmp_max(CONST_MIN, cmp_max(CONST_MAX, input));
++ let x23 = cmp_min(CONST_MAX, cmp_min(CONST_MIN, input));
++ let input: f64 = cmp_min_max(1) as f64;
++ let x24 = f64::max(f64::max(input, CONST_F64_MAX), CONST_F64_MIN);
++ let x25 = f64::min(f64::min(input, CONST_F64_MIN), CONST_F64_MAX);
++ let x26 = f64::max(CONST_F64_MIN, f64::max(input, CONST_F64_MAX));
++ let x27 = f64::min(CONST_F64_MAX, f64::min(input, CONST_F64_MIN));
++ let x28 = f64::max(f64::max(CONST_F64_MAX, input), CONST_F64_MIN);
++ let x29 = f64::min(f64::min(CONST_F64_MIN, input), CONST_F64_MAX);
++ let x30 = f64::max(CONST_F64_MIN, f64::max(CONST_F64_MAX, input));
++ let x31 = f64::min(CONST_F64_MAX, f64::min(CONST_F64_MIN, input));
++ let x32 = f64::min(CONST_F64_MAX, f64::min(CONST_F64_MIN, CONST_F64_MAX));
++ }
++}
++
++fn dont_tell_me_what_to_do() {
++ let (input, min, max) = (0, -2, 3);
++ let mut x_never = input;
++ #[allow(clippy::manual_clamp)]
++ if x_never < min {
++ x_never = min;
++ }
++ if x_never > max {
++ x_never = max;
++ }
++}
++
++/// Just to ensure this isn't const evaled
++fn cmp_min_max(input: i32) -> i32 {
++ input * 3
++}
--- /dev/null
--- /dev/null
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:76:5
++ |
++LL | / if x9 < min {
++LL | | x9 = min;
++LL | | }
++LL | | if x9 > max {
++LL | | x9 = max;
++LL | | }
++ | |_____^ help: replace with clamp: `x9 = x9.clamp(min, max);`
++ |
++ = note: clamp will panic if max < min
++ = note: `-D clippy::manual-clamp` implied by `-D warnings`
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:91:5
++ |
++LL | / if x11 > max {
++LL | | x11 = max;
++LL | | }
++LL | | if x11 < min {
++LL | | x11 = min;
++LL | | }
++ | |_____^ help: replace with clamp: `x11 = x11.clamp(min, max);`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:99:5
++ |
++LL | / if min > x12 {
++LL | | x12 = min;
++LL | | }
++LL | | if max < x12 {
++LL | | x12 = max;
++LL | | }
++ | |_____^ help: replace with clamp: `x12 = x12.clamp(min, max);`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:107:5
++ |
++LL | / if max < x13 {
++LL | | x13 = max;
++LL | | }
++LL | | if min > x13 {
++LL | | x13 = min;
++LL | | }
++ | |_____^ help: replace with clamp: `x13 = x13.clamp(min, max);`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:161:5
++ |
++LL | / if max < x33 {
++LL | | x33 = max;
++LL | | }
++LL | | if min > x33 {
++LL | | x33 = min;
++LL | | }
++ | |_____^ help: replace with clamp: `x33 = x33.clamp(min, max);`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:21:14
++ |
++LL | let x0 = if max < input {
++ | ______________^
++LL | | max
++LL | | } else if min > input {
++LL | | min
++LL | | } else {
++LL | | input
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:29:14
++ |
++LL | let x1 = if input > max {
++ | ______________^
++LL | | max
++LL | | } else if input < min {
++LL | | min
++LL | | } else {
++LL | | input
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:37:14
++ |
++LL | let x2 = if input < min {
++ | ______________^
++LL | | min
++LL | | } else if input > max {
++LL | | max
++LL | | } else {
++LL | | input
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:45:14
++ |
++LL | let x3 = if min > input {
++ | ______________^
++LL | | min
++LL | | } else if max < input {
++LL | | max
++LL | | } else {
++LL | | input
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:53:14
++ |
++LL | let x4 = input.max(min).min(max);
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:55:14
++ |
++LL | let x5 = input.min(max).max(min);
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:57:14
++ |
++LL | let x6 = match input {
++ | ______________^
++LL | | x if x > max => max,
++LL | | x if x < min => min,
++LL | | x => x,
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:63:14
++ |
++LL | let x7 = match input {
++ | ______________^
++LL | | x if x < min => min,
++LL | | x if x > max => max,
++LL | | x => x,
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:69:14
++ |
++LL | let x8 = match input {
++ | ______________^
++LL | | x if max < x => max,
++LL | | x if min > x => min,
++LL | | x => x,
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:83:15
++ |
++LL | let x10 = match input {
++ | _______________^
++LL | | x if min > x => min,
++LL | | x if max < x => max,
++LL | | x => x,
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:114:15
++ |
++LL | let x14 = if input > CONST_MAX {
++ | _______________^
++LL | | CONST_MAX
++LL | | } else if input < CONST_MIN {
++LL | | CONST_MIN
++LL | | } else {
++LL | | input
++LL | | };
++ | |_____^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:123:19
++ |
++LL | let x15 = if input > max {
++ | ___________________^
++LL | | max
++LL | | } else if input < min {
++LL | | min
++LL | | } else {
++LL | | input
++LL | | };
++ | |_________^ help: replace with clamp: `input.clamp(min, max)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:134:19
++ |
++LL | let x16 = cmp_max(cmp_min(input, CONST_MAX), CONST_MIN);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:135:19
++ |
++LL | let x17 = cmp_min(cmp_max(input, CONST_MIN), CONST_MAX);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:136:19
++ |
++LL | let x18 = cmp_max(CONST_MIN, cmp_min(input, CONST_MAX));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:137:19
++ |
++LL | let x19 = cmp_min(CONST_MAX, cmp_max(input, CONST_MIN));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:138:19
++ |
++LL | let x20 = cmp_max(cmp_min(CONST_MAX, input), CONST_MIN);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:139:19
++ |
++LL | let x21 = cmp_min(cmp_max(CONST_MIN, input), CONST_MAX);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:140:19
++ |
++LL | let x22 = cmp_max(CONST_MIN, cmp_min(CONST_MAX, input));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:141:19
++ |
++LL | let x23 = cmp_min(CONST_MAX, cmp_max(CONST_MIN, input));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)`
++ |
++ = note: clamp will panic if max < min
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:143:19
++ |
++LL | let x24 = f64::max(f64::min(input, CONST_F64_MAX), CONST_F64_MIN);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:144:19
++ |
++LL | let x25 = f64::min(f64::max(input, CONST_F64_MIN), CONST_F64_MAX);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:145:19
++ |
++LL | let x26 = f64::max(CONST_F64_MIN, f64::min(input, CONST_F64_MAX));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:146:19
++ |
++LL | let x27 = f64::min(CONST_F64_MAX, f64::max(input, CONST_F64_MIN));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:147:19
++ |
++LL | let x28 = f64::max(f64::min(CONST_F64_MAX, input), CONST_F64_MIN);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:148:19
++ |
++LL | let x29 = f64::min(f64::max(CONST_F64_MIN, input), CONST_F64_MAX);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:149:19
++ |
++LL | let x30 = f64::max(CONST_F64_MIN, f64::min(CONST_F64_MAX, input));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:150:19
++ |
++LL | let x31 = f64::min(CONST_F64_MAX, f64::max(CONST_F64_MIN, input));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)`
++ |
++ = note: clamp will panic if max < min, min.is_nan(), or max.is_nan()
++ = note: clamp returns NaN if the input is NaN
++
++error: clamp-like pattern without using clamp function
++ --> $DIR/manual_clamp.rs:153:5
++ |
++LL | / if x32 < min {
++LL | | x32 = min;
++LL | | } else if x32 > max {
++LL | | x32 = max;
++LL | | }
++ | |_____^ help: replace with clamp: `x32 = x32.clamp(min, max);`
++ |
++ = note: clamp will panic if max < min
++
++error: aborting due to 34 previous errors
++
--- /dev/null
-
- #![allow(unused, clippy::needless_return)]
+// run-rustfix
+#![warn(clippy::manual_find)]
++#![allow(unused)]
++#![allow(clippy::needless_return, clippy::uninlined_format_args)]
+
+use std::collections::HashMap;
+
+const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3];
+
+fn lookup(n: u32) -> Option<u32> {
+ ARRAY.iter().find(|&&v| v == n).copied()
+}
+
+fn with_pat(arr: Vec<(u32, u32)>) -> Option<u32> {
+ arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0)
+}
+
+struct Data {
+ name: String,
+ is_true: bool,
+}
+fn with_struct(arr: Vec<Data>) -> Option<Data> {
+ arr.into_iter().find(|el| el.name.len() == 10)
+}
+
+struct Tuple(usize, usize);
+fn with_tuple_struct(arr: Vec<Tuple>) -> Option<usize> {
+ arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3)
+}
+
+struct A;
+impl A {
+ fn should_keep(&self) -> bool {
+ true
+ }
+}
+fn with_method_call(arr: Vec<A>) -> Option<A> {
+ arr.into_iter().find(|el| el.should_keep())
+}
+
+fn with_closure(arr: Vec<u32>) -> Option<u32> {
+ let f = |el: u32| -> u32 { el + 10 };
+ arr.into_iter().find(|&el| f(el) == 20)
+}
+
+fn with_closure2(arr: HashMap<String, i32>) -> Option<i32> {
+ let f = |el: i32| -> bool { el == 10 };
+ arr.values().find(|&&el| f(el)).copied()
+}
+
+fn with_bool(arr: Vec<Data>) -> Option<Data> {
+ arr.into_iter().find(|el| el.is_true)
+}
+
+fn with_side_effects(arr: Vec<u32>) -> Option<u32> {
+ for v in arr {
+ if v == 1 {
+ println!("side effect");
+ return Some(v);
+ }
+ }
+ None
+}
+
+fn with_else(arr: Vec<u32>) -> Option<u32> {
+ for el in arr {
+ if el % 2 == 0 {
+ return Some(el);
+ } else {
+ println!("{}", el);
+ }
+ }
+ None
+}
+
+fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option<u8> {
+ v.into_iter().map(|(_, &x)| x).find(|&x| x > 10)
+}
+
+fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option<u8> {
+ v.iter().map(|&(_, &x)| x).find(|&x| x > 10)
+}
+
+fn explicit_ret(arr: Vec<i32>) -> Option<i32> {
+ arr.into_iter().find(|&x| x >= 5)
+}
+
+fn plus_one(a: i32) -> Option<i32> {
+ Some(a + 1)
+}
+fn fn_instead_of_some(a: &[i32]) -> Option<i32> {
+ for &x in a {
+ if x == 1 {
+ return plus_one(x);
+ }
+ }
+ None
+}
+
+fn for_in_condition(a: &[i32], b: bool) -> Option<i32> {
+ if b {
+ for &x in a {
+ if x == 1 {
+ return Some(x);
+ }
+ }
+ }
+ None
+}
+
+fn intermediate_statements(a: &[i32]) -> Option<i32> {
+ for &x in a {
+ if x == 1 {
+ return Some(x);
+ }
+ }
+
+ println!("side effect");
+
+ None
+}
+
+fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option<i32> {
+ for (x, mut s) in arr {
+ if x == 1 && s.as_mut_str().len() == 2 {
+ return Some(x);
+ }
+ }
+ None
+}
+
+fn as_closure() {
+ #[rustfmt::skip]
+ let f = |arr: Vec<i32>| -> Option<i32> {
+ arr.into_iter().find(|&x| x < 1)
+ };
+}
+
+fn in_block(a: &[i32]) -> Option<i32> {
+ let should_be_none = {
+ for &x in a {
+ if x == 1 {
+ return Some(x);
+ }
+ }
+ None
+ };
+
+ assert!(should_be_none.is_none());
+
+ should_be_none
+}
+
+// Not handled yet
+fn mut_binding(v: Vec<String>) -> Option<String> {
+ for mut s in v {
+ if s.as_mut_str().len() > 1 {
+ return Some(s);
+ }
+ }
+ None
+}
+
+fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> {
+ for a @ [first, ..] in v {
+ if a[12] == first {
+ return Some(a);
+ }
+ }
+ None
+}
+
+fn two_bindings(v: Vec<(u8, u8)>) -> Option<u8> {
+ for (a, n) in v {
+ if a == n {
+ return Some(a);
+ }
+ }
+ None
+}
+
+fn main() {}
--- /dev/null
-
- #![allow(unused, clippy::needless_return)]
+// run-rustfix
+#![warn(clippy::manual_find)]
++#![allow(unused)]
++#![allow(clippy::needless_return, clippy::uninlined_format_args)]
+
+use std::collections::HashMap;
+
+const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3];
+
+fn lookup(n: u32) -> Option<u32> {
+ for &v in ARRAY {
+ if v == n {
+ return Some(v);
+ }
+ }
+ None
+}
+
+fn with_pat(arr: Vec<(u32, u32)>) -> Option<u32> {
+ for (a, _) in arr {
+ if a % 2 == 0 {
+ return Some(a);
+ }
+ }
+ None
+}
+
+struct Data {
+ name: String,
+ is_true: bool,
+}
+fn with_struct(arr: Vec<Data>) -> Option<Data> {
+ for el in arr {
+ if el.name.len() == 10 {
+ return Some(el);
+ }
+ }
+ None
+}
+
+struct Tuple(usize, usize);
+fn with_tuple_struct(arr: Vec<Tuple>) -> Option<usize> {
+ for Tuple(a, _) in arr {
+ if a >= 3 {
+ return Some(a);
+ }
+ }
+ None
+}
+
+struct A;
+impl A {
+ fn should_keep(&self) -> bool {
+ true
+ }
+}
+fn with_method_call(arr: Vec<A>) -> Option<A> {
+ for el in arr {
+ if el.should_keep() {
+ return Some(el);
+ }
+ }
+ None
+}
+
+fn with_closure(arr: Vec<u32>) -> Option<u32> {
+ let f = |el: u32| -> u32 { el + 10 };
+ for el in arr {
+ if f(el) == 20 {
+ return Some(el);
+ }
+ }
+ None
+}
+
+fn with_closure2(arr: HashMap<String, i32>) -> Option<i32> {
+ let f = |el: i32| -> bool { el == 10 };
+ for &el in arr.values() {
+ if f(el) {
+ return Some(el);
+ }
+ }
+ None
+}
+
+fn with_bool(arr: Vec<Data>) -> Option<Data> {
+ for el in arr {
+ if el.is_true {
+ return Some(el);
+ }
+ }
+ None
+}
+
+fn with_side_effects(arr: Vec<u32>) -> Option<u32> {
+ for v in arr {
+ if v == 1 {
+ println!("side effect");
+ return Some(v);
+ }
+ }
+ None
+}
+
+fn with_else(arr: Vec<u32>) -> Option<u32> {
+ for el in arr {
+ if el % 2 == 0 {
+ return Some(el);
+ } else {
+ println!("{}", el);
+ }
+ }
+ None
+}
+
+fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option<u8> {
+ for (_, &x) in v {
+ if x > 10 {
+ return Some(x);
+ }
+ }
+ None
+}
+
+fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option<u8> {
+ for &(_, &x) in v {
+ if x > 10 {
+ return Some(x);
+ }
+ }
+ None
+}
+
+fn explicit_ret(arr: Vec<i32>) -> Option<i32> {
+ for x in arr {
+ if x >= 5 {
+ return Some(x);
+ }
+ }
+ return None;
+}
+
+fn plus_one(a: i32) -> Option<i32> {
+ Some(a + 1)
+}
+fn fn_instead_of_some(a: &[i32]) -> Option<i32> {
+ for &x in a {
+ if x == 1 {
+ return plus_one(x);
+ }
+ }
+ None
+}
+
+fn for_in_condition(a: &[i32], b: bool) -> Option<i32> {
+ if b {
+ for &x in a {
+ if x == 1 {
+ return Some(x);
+ }
+ }
+ }
+ None
+}
+
+fn intermediate_statements(a: &[i32]) -> Option<i32> {
+ for &x in a {
+ if x == 1 {
+ return Some(x);
+ }
+ }
+
+ println!("side effect");
+
+ None
+}
+
+fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option<i32> {
+ for (x, mut s) in arr {
+ if x == 1 && s.as_mut_str().len() == 2 {
+ return Some(x);
+ }
+ }
+ None
+}
+
+fn as_closure() {
+ #[rustfmt::skip]
+ let f = |arr: Vec<i32>| -> Option<i32> {
+ for x in arr {
+ if x < 1 {
+ return Some(x);
+ }
+ }
+ None
+ };
+}
+
+fn in_block(a: &[i32]) -> Option<i32> {
+ let should_be_none = {
+ for &x in a {
+ if x == 1 {
+ return Some(x);
+ }
+ }
+ None
+ };
+
+ assert!(should_be_none.is_none());
+
+ should_be_none
+}
+
+// Not handled yet
+fn mut_binding(v: Vec<String>) -> Option<String> {
+ for mut s in v {
+ if s.as_mut_str().len() > 1 {
+ return Some(s);
+ }
+ }
+ None
+}
+
+fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> {
+ for a @ [first, ..] in v {
+ if a[12] == first {
+ return Some(a);
+ }
+ }
+ None
+}
+
+fn two_bindings(v: Vec<(u8, u8)>) -> Option<u8> {
+ for (a, n) in v {
+ if a == n {
+ return Some(a);
+ }
+ }
+ None
+}
+
+fn main() {}
--- /dev/null
- #![allow(clippy::useless_vec)]
+#![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 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
-
+// aux-build:option_helpers.rs
+#![warn(clippy::map_unwrap_or)]
++#![allow(clippy::uninlined_format_args)]
+
+#[macro_use]
+extern crate option_helpers;
+
+use std::collections::HashMap;
+
+#[rustfmt::skip]
+fn option_methods() {
+ let opt = Some(1);
+
+ // Check for `option.map(_).unwrap_or(_)` use.
+ // Single line case.
+ let _ = opt.map(|x| x + 1)
+ // Should lint even though this call is on a separate line.
+ .unwrap_or(0);
+ // Multi-line cases.
+ let _ = opt.map(|x| {
+ x + 1
+ }
+ ).unwrap_or(0);
+ let _ = opt.map(|x| x + 1)
+ .unwrap_or({
+ 0
+ });
+ // Single line `map(f).unwrap_or(None)` case.
+ let _ = opt.map(|x| Some(x + 1)).unwrap_or(None);
+ // Multi-line `map(f).unwrap_or(None)` cases.
+ let _ = opt.map(|x| {
+ Some(x + 1)
+ }
+ ).unwrap_or(None);
+ let _ = opt
+ .map(|x| Some(x + 1))
+ .unwrap_or(None);
+ // macro case
+ let _ = opt_map!(opt, |x| x + 1).unwrap_or(0); // should not lint
+
+ // Should not lint if not copyable
+ let id: String = "identifier".to_string();
+ let _ = Some("prefix").map(|p| format!("{}.{}", p, id)).unwrap_or(id);
+ // ...but DO lint if the `unwrap_or` argument is not used in the `map`
+ let id: String = "identifier".to_string();
+ let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id);
+
+ // Check for `option.map(_).unwrap_or_else(_)` use.
+ // Multi-line cases.
+ let _ = opt.map(|x| {
+ x + 1
+ }
+ ).unwrap_or_else(|| 0);
+ let _ = opt.map(|x| x + 1)
+ .unwrap_or_else(||
+ 0
+ );
+}
+
+#[rustfmt::skip]
+fn result_methods() {
+ let res: Result<i32, ()> = Ok(1);
+
+ // Check for `result.map(_).unwrap_or_else(_)` use.
+ // multi line cases
+ let _ = res.map(|x| {
+ x + 1
+ }
+ ).unwrap_or_else(|_e| 0);
+ let _ = res.map(|x| x + 1)
+ .unwrap_or_else(|_e| {
+ 0
+ });
+ // macro case
+ let _ = opt_map!(res, |x| x + 1).unwrap_or_else(|_e| 0); // should not lint
+}
+
+fn main() {
+ option_methods();
+ result_methods();
+}
--- /dev/null
- #![allow(dead_code, unused_variables, clippy::equatable_if_let, clippy::enum_variant_names)]
+// run-rustfix
+#![warn(clippy::match_ref_pats)]
++#![allow(dead_code, unused_variables)]
++#![allow(clippy::enum_variant_names, clippy::equatable_if_let, clippy::uninlined_format_args)]
+
+fn ref_pats() {
+ {
+ let v = &Some(0);
+ match *v {
+ Some(v) => println!("{:?}", v),
+ None => println!("none"),
+ }
+ match v {
+ // This doesn't trigger; we have a different pattern.
+ &Some(v) => println!("some"),
+ other => println!("other"),
+ }
+ }
+ let tup = &(1, 2);
+ match tup {
+ &(v, 1) => println!("{}", v),
+ _ => println!("none"),
+ }
+ // Special case: using `&` both in expr and pats.
+ let w = Some(0);
+ match w {
+ Some(v) => println!("{:?}", v),
+ None => println!("none"),
+ }
+ // False positive: only wildcard pattern.
+ let w = Some(0);
+ #[allow(clippy::match_single_binding)]
+ match w {
+ _ => println!("none"),
+ }
+
+ let a = &Some(0);
+ if a.is_none() {
+ println!("none");
+ }
+
+ let b = Some(0);
+ if b.is_none() {
+ println!("none");
+ }
+}
+
+mod ice_3719 {
+ macro_rules! foo_variant(
+ ($idx:expr) => (Foo::get($idx).unwrap())
+ );
+
+ enum Foo {
+ A,
+ B,
+ }
+
+ impl Foo {
+ fn get(idx: u8) -> Option<&'static Self> {
+ match idx {
+ 0 => Some(&Foo::A),
+ 1 => Some(&Foo::B),
+ _ => None,
+ }
+ }
+ }
+
+ fn ice_3719() {
+ // ICE #3719
+ match foo_variant!(0) {
+ &Foo::A => println!("A"),
+ _ => println!("Wild"),
+ }
+ }
+}
+
+mod issue_7740 {
+ macro_rules! foobar_variant(
+ ($idx:expr) => (FooBar::get($idx).unwrap())
+ );
+
+ enum FooBar {
+ Foo,
+ Bar,
+ FooBar,
+ BarFoo,
+ }
+
+ impl FooBar {
+ fn get(idx: u8) -> Option<&'static Self> {
+ match idx {
+ 0 => Some(&FooBar::Foo),
+ 1 => Some(&FooBar::Bar),
+ 2 => Some(&FooBar::FooBar),
+ 3 => Some(&FooBar::BarFoo),
+ _ => None,
+ }
+ }
+ }
+
+ fn issue_7740() {
+ // Issue #7740
+ match *foobar_variant!(0) {
+ FooBar::Foo => println!("Foo"),
+ FooBar::Bar => println!("Bar"),
+ FooBar::FooBar => println!("FooBar"),
+ _ => println!("Wild"),
+ }
+
+ // This shouldn't trigger
+ if let &FooBar::BarFoo = foobar_variant!(3) {
+ println!("BarFoo");
+ } else {
+ println!("Wild");
+ }
+ }
+}
+
+fn main() {}
--- /dev/null
- #![allow(dead_code, unused_variables, clippy::equatable_if_let, clippy::enum_variant_names)]
+// run-rustfix
+#![warn(clippy::match_ref_pats)]
++#![allow(dead_code, unused_variables)]
++#![allow(clippy::enum_variant_names, clippy::equatable_if_let, clippy::uninlined_format_args)]
+
+fn ref_pats() {
+ {
+ let v = &Some(0);
+ match v {
+ &Some(v) => println!("{:?}", v),
+ &None => println!("none"),
+ }
+ match v {
+ // This doesn't trigger; we have a different pattern.
+ &Some(v) => println!("some"),
+ other => println!("other"),
+ }
+ }
+ let tup = &(1, 2);
+ match tup {
+ &(v, 1) => println!("{}", v),
+ _ => println!("none"),
+ }
+ // Special case: using `&` both in expr and pats.
+ let w = Some(0);
+ match &w {
+ &Some(v) => println!("{:?}", v),
+ &None => println!("none"),
+ }
+ // False positive: only wildcard pattern.
+ let w = Some(0);
+ #[allow(clippy::match_single_binding)]
+ match w {
+ _ => println!("none"),
+ }
+
+ let a = &Some(0);
+ if let &None = a {
+ println!("none");
+ }
+
+ let b = Some(0);
+ if let &None = &b {
+ println!("none");
+ }
+}
+
+mod ice_3719 {
+ macro_rules! foo_variant(
+ ($idx:expr) => (Foo::get($idx).unwrap())
+ );
+
+ enum Foo {
+ A,
+ B,
+ }
+
+ impl Foo {
+ fn get(idx: u8) -> Option<&'static Self> {
+ match idx {
+ 0 => Some(&Foo::A),
+ 1 => Some(&Foo::B),
+ _ => None,
+ }
+ }
+ }
+
+ fn ice_3719() {
+ // ICE #3719
+ match foo_variant!(0) {
+ &Foo::A => println!("A"),
+ _ => println!("Wild"),
+ }
+ }
+}
+
+mod issue_7740 {
+ macro_rules! foobar_variant(
+ ($idx:expr) => (FooBar::get($idx).unwrap())
+ );
+
+ enum FooBar {
+ Foo,
+ Bar,
+ FooBar,
+ BarFoo,
+ }
+
+ impl FooBar {
+ fn get(idx: u8) -> Option<&'static Self> {
+ match idx {
+ 0 => Some(&FooBar::Foo),
+ 1 => Some(&FooBar::Bar),
+ 2 => Some(&FooBar::FooBar),
+ 3 => Some(&FooBar::BarFoo),
+ _ => None,
+ }
+ }
+ }
+
+ fn issue_7740() {
+ // Issue #7740
+ match foobar_variant!(0) {
+ &FooBar::Foo => println!("Foo"),
+ &FooBar::Bar => println!("Bar"),
+ &FooBar::FooBar => println!("FooBar"),
+ _ => println!("Wild"),
+ }
+
+ // This shouldn't trigger
+ if let &FooBar::BarFoo = foobar_variant!(3) {
+ println!("BarFoo");
+ } else {
+ println!("Wild");
+ }
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/match_ref_pats.rs:8:9
+error: you don't need to add `&` to all patterns
- --> $DIR/match_ref_pats.rs:25:5
++ --> $DIR/match_ref_pats.rs:9:9
+ |
+LL | / match v {
+LL | | &Some(v) => println!("{:?}", v),
+LL | | &None => println!("none"),
+LL | | }
+ | |_________^
+ |
+ = note: `-D clippy::match-ref-pats` implied by `-D warnings`
+help: instead of prefixing all patterns with `&`, you can dereference the expression
+ |
+LL ~ match *v {
+LL ~ Some(v) => println!("{:?}", v),
+LL ~ None => println!("none"),
+ |
+
+error: you don't need to add `&` to both the expression and the patterns
- --> $DIR/match_ref_pats.rs:37:12
++ --> $DIR/match_ref_pats.rs:26:5
+ |
+LL | / match &w {
+LL | | &Some(v) => println!("{:?}", v),
+LL | | &None => println!("none"),
+LL | | }
+ | |_____^
+ |
+help: try
+ |
+LL ~ match w {
+LL ~ Some(v) => println!("{:?}", v),
+LL ~ None => println!("none"),
+ |
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/match_ref_pats.rs:42:12
++ --> $DIR/match_ref_pats.rs:38:12
+ |
+LL | if let &None = a {
+ | -------^^^^^---- help: try this: `if a.is_none()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: redundant pattern matching, consider using `is_none()`
- --> $DIR/match_ref_pats.rs:102:9
++ --> $DIR/match_ref_pats.rs:43:12
+ |
+LL | if let &None = &b {
+ | -------^^^^^----- help: try this: `if b.is_none()`
+
+error: you don't need to add `&` to all patterns
++ --> $DIR/match_ref_pats.rs:103:9
+ |
+LL | / match foobar_variant!(0) {
+LL | | &FooBar::Foo => println!("Foo"),
+LL | | &FooBar::Bar => println!("Bar"),
+LL | | &FooBar::FooBar => println!("FooBar"),
+LL | | _ => println!("Wild"),
+LL | | }
+ | |_________^
+ |
+help: instead of prefixing all patterns with `&`, you can dereference the expression
+ |
+LL ~ match *foobar_variant!(0) {
+LL ~ FooBar::Foo => println!("Foo"),
+LL ~ FooBar::Bar => println!("Bar"),
+LL ~ FooBar::FooBar => println!("FooBar"),
+ |
+
+error: aborting due to 5 previous errors
+
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::boxed_local)]
+#![warn(clippy::match_result_ok)]
+#![allow(dead_code)]
++#![allow(clippy::boxed_local, clippy::uninlined_format_args)]
+
+// Checking `if` cases
+
+fn str_to_int(x: &str) -> i32 {
+ if let Ok(y) = x.parse() { y } else { 0 }
+}
+
+fn str_to_int_ok(x: &str) -> i32 {
+ if let Ok(y) = x.parse() { y } else { 0 }
+}
+
+#[rustfmt::skip]
+fn strange_some_no_else(x: &str) -> i32 {
+ {
+ if let Ok(y) = x . parse() {
+ return y;
+ };
+ 0
+ }
+}
+
+// Checking `while` cases
+
+struct Wat {
+ counter: i32,
+}
+
+impl Wat {
+ fn next(&mut self) -> Result<i32, &str> {
+ self.counter += 1;
+ if self.counter < 5 {
+ Ok(self.counter)
+ } else {
+ Err("Oh no")
+ }
+ }
+}
+
+fn base_1(x: i32) {
+ let mut wat = Wat { counter: x };
+ while let Ok(a) = wat.next() {
+ println!("{}", a);
+ }
+}
+
+fn base_2(x: i32) {
+ let mut wat = Wat { counter: x };
+ while let Ok(a) = wat.next() {
+ println!("{}", a);
+ }
+}
+
+fn base_3(test_func: Box<Result<i32, &str>>) {
+ // Expected to stay as is
+ while let Some(_b) = test_func.ok() {}
+}
+
+fn main() {}
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::boxed_local)]
+#![warn(clippy::match_result_ok)]
+#![allow(dead_code)]
++#![allow(clippy::boxed_local, clippy::uninlined_format_args)]
+
+// Checking `if` cases
+
+fn str_to_int(x: &str) -> i32 {
+ if let Some(y) = x.parse().ok() { y } else { 0 }
+}
+
+fn str_to_int_ok(x: &str) -> i32 {
+ if let Ok(y) = x.parse() { y } else { 0 }
+}
+
+#[rustfmt::skip]
+fn strange_some_no_else(x: &str) -> i32 {
+ {
+ if let Some(y) = x . parse() . ok () {
+ return y;
+ };
+ 0
+ }
+}
+
+// Checking `while` cases
+
+struct Wat {
+ counter: i32,
+}
+
+impl Wat {
+ fn next(&mut self) -> Result<i32, &str> {
+ self.counter += 1;
+ if self.counter < 5 {
+ Ok(self.counter)
+ } else {
+ Err("Oh no")
+ }
+ }
+}
+
+fn base_1(x: i32) {
+ let mut wat = Wat { counter: x };
+ while let Some(a) = wat.next().ok() {
+ println!("{}", a);
+ }
+}
+
+fn base_2(x: i32) {
+ let mut wat = Wat { counter: x };
+ while let Ok(a) = wat.next() {
+ println!("{}", a);
+ }
+}
+
+fn base_3(test_func: Box<Result<i32, &str>>) {
+ // Expected to stay as is
+ while let Some(_b) = test_func.ok() {}
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/match_result_ok.rs:10:5
+error: matching on `Some` with `ok()` is redundant
- --> $DIR/match_result_ok.rs:20:9
++ --> $DIR/match_result_ok.rs:9:5
+ |
+LL | if let Some(y) = x.parse().ok() { y } else { 0 }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::match-result-ok` implied by `-D warnings`
+help: consider matching on `Ok(y)` and removing the call to `ok` instead
+ |
+LL | if let Ok(y) = x.parse() { y } else { 0 }
+ | ~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: matching on `Some` with `ok()` is redundant
- --> $DIR/match_result_ok.rs:46:5
++ --> $DIR/match_result_ok.rs:19:9
+ |
+LL | if let Some(y) = x . parse() . ok () {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider matching on `Ok(y)` and removing the call to `ok` instead
+ |
+LL | if let Ok(y) = x . parse() {
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: matching on `Some` with `ok()` is redundant
++ --> $DIR/match_result_ok.rs:45:5
+ |
+LL | while let Some(a) = wat.next().ok() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider matching on `Ok(a)` and removing the call to `ok` instead
+ |
+LL | while let Ok(a) = wat.next() {
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- #![allow(clippy::disallowed_names, clippy::diverging_sub_expression)]
+#![warn(clippy::match_same_arms)]
++#![allow(
++ clippy::disallowed_names,
++ clippy::diverging_sub_expression,
++ clippy::uninlined_format_args
++)]
+
+fn bar<T>(_: T) {}
+fn foo() -> bool {
+ unimplemented!()
+}
+
+fn match_same_arms() {
+ let _ = match 42 {
+ 42 => {
+ foo();
+ let mut a = 42 + [23].len() as i32;
+ if true {
+ a += 7;
+ }
+ a = -31 - a;
+ a
+ },
+ _ => {
+ //~ ERROR match arms have same body
+ foo();
+ let mut a = 42 + [23].len() as i32;
+ if true {
+ a += 7;
+ }
+ a = -31 - a;
+ a
+ },
+ };
+
+ let _ = match 42 {
+ 42 => foo(),
+ 51 => foo(), //~ ERROR match arms have same body
+ _ => true,
+ };
+
+ let _ = match Some(42) {
+ Some(_) => 24,
+ None => 24, //~ ERROR match arms have same body
+ };
+
+ let _ = match Some(42) {
+ Some(foo) => 24,
+ None => 24,
+ };
+
+ let _ = match Some(42) {
+ Some(42) => 24,
+ Some(a) => 24, // bindings are different
+ None => 0,
+ };
+
+ let _ = match Some(42) {
+ Some(a) if a > 0 => 24,
+ Some(a) => 24, // one arm has a guard
+ None => 0,
+ };
+
+ match (Some(42), Some(42)) {
+ (Some(a), None) => bar(a),
+ (None, Some(a)) => bar(a), //~ ERROR match arms have same body
+ _ => (),
+ }
+
+ match (Some(42), Some(42)) {
+ (Some(a), ..) => bar(a),
+ (.., Some(a)) => bar(a), //~ ERROR match arms have same body
+ _ => (),
+ }
+
+ let _ = match Some(()) {
+ Some(()) => 0.0,
+ None => -0.0,
+ };
+
+ match (Some(42), Some("")) {
+ (Some(a), None) => bar(a),
+ (None, Some(a)) => bar(a), // bindings have different types
+ _ => (),
+ }
+
+ let x: Result<i32, &str> = Ok(3);
+
+ // No warning because of the guard.
+ match x {
+ Ok(x) if x * x == 64 => println!("ok"),
+ Ok(_) => println!("ok"),
+ Err(_) => println!("err"),
+ }
+
+ // This used to be a false positive; see issue #1996.
+ match x {
+ Ok(3) => println!("ok"),
+ Ok(x) if x * x == 64 => println!("ok 64"),
+ Ok(_) => println!("ok"),
+ Err(_) => println!("err"),
+ }
+
+ match (x, Some(1i32)) {
+ (Ok(x), Some(_)) => println!("ok {}", x),
+ (Ok(_), Some(x)) => println!("ok {}", x),
+ _ => println!("err"),
+ }
+
+ // No warning; different types for `x`.
+ match (x, Some(1.0f64)) {
+ (Ok(x), Some(_)) => println!("ok {}", x),
+ (Ok(_), Some(x)) => println!("ok {}", x),
+ _ => println!("err"),
+ }
+
+ // False negative #2251.
+ match x {
+ Ok(_tmp) => println!("ok"),
+ Ok(3) => println!("ok"),
+ Ok(_) => println!("ok"),
+ Err(_) => {
+ unreachable!();
+ },
+ }
+
+ // False positive #1390
+ macro_rules! empty {
+ ($e:expr) => {};
+ }
+ match 0 {
+ 0 => {
+ empty!(0);
+ },
+ 1 => {
+ empty!(1);
+ },
+ x => {
+ empty!(x);
+ },
+ };
+
+ // still lint if the tokens are the same
+ match 0 {
+ 0 => {
+ empty!(0);
+ },
+ 1 => {
+ empty!(0);
+ },
+ x => {
+ empty!(x);
+ },
+ }
+
+ match_expr_like_matches_macro_priority();
+}
+
+fn match_expr_like_matches_macro_priority() {
+ enum E {
+ A,
+ B,
+ C,
+ }
+ let x = E::A;
+ let _ans = match x {
+ E::A => false,
+ E::B => false,
+ _ => true,
+ };
+}
+
+fn main() {
+ let _ = match Some(0) {
+ Some(0) => 0,
+ Some(1) => 1,
+ #[cfg(feature = "foo")]
+ Some(2) => 2,
+ _ => 1,
+ };
+
+ enum Foo {
+ X(u32),
+ Y(u32),
+ Z(u32),
+ }
+
+ // Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between.
+ let _ = match Foo::X(0) {
+ Foo::X(0) => 1,
+ Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2,
+ Foo::Z(_) => 1,
+ _ => 0,
+ };
+
+ // Suggest moving `Foo::Z(_)` up.
+ let _ = match Foo::X(0) {
+ Foo::X(0) => 1,
+ Foo::X(_) | Foo::Y(_) => 2,
+ Foo::Z(_) => 1,
+ _ => 0,
+ };
+
+ // Suggest moving `Foo::X(0)` down.
+ let _ = match Foo::X(0) {
+ Foo::X(0) => 1,
+ Foo::Y(_) | Foo::Z(0) => 2,
+ Foo::Z(_) => 1,
+ _ => 0,
+ };
+
+ // Don't lint.
+ let _ = match 0 {
+ -2 => 1,
+ -5..=50 => 2,
+ -150..=88 => 1,
+ _ => 3,
+ };
+
+ struct Bar {
+ x: u32,
+ y: u32,
+ z: u32,
+ }
+
+ // Lint.
+ let _ = match None {
+ Some(Bar { x: 0, y: 5, .. }) => 1,
+ Some(Bar { y: 10, z: 0, .. }) => 2,
+ None => 50,
+ Some(Bar { y: 0, x: 5, .. }) => 1,
+ _ => 200,
+ };
+
+ let _ = match 0 {
+ 0 => todo!(),
+ 1 => todo!(),
+ 2 => core::convert::identity::<u32>(todo!()),
+ 3 => core::convert::identity::<u32>(todo!()),
+ _ => 5,
+ };
+}
--- /dev/null
- --> $DIR/match_same_arms2.rs:11:9
+error: this match arm has an identical body to the `_` wildcard arm
- --> $DIR/match_same_arms2.rs:20:9
++ --> $DIR/match_same_arms2.rs:15:9
+ |
+LL | / 42 => {
+LL | | foo();
+LL | | let mut a = 42 + [23].len() as i32;
+LL | | if true {
+... |
+LL | | a
+LL | | },
+ | |_________^ help: try removing the arm
+ |
+ = help: or try changing either arm body
+note: `_` wildcard arm here
- --> $DIR/match_same_arms2.rs:34:9
++ --> $DIR/match_same_arms2.rs:24:9
+ |
+LL | / _ => {
+LL | | //~ ERROR match arms have same body
+LL | | foo();
+LL | | let mut a = 42 + [23].len() as i32;
+... |
+LL | | a
+LL | | },
+ | |_________^
+ = note: `-D clippy::match-same-arms` implied by `-D warnings`
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:33:9
++ --> $DIR/match_same_arms2.rs:38:9
+ |
+LL | 51 => foo(), //~ ERROR match arms have same body
+ | --^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `51 | 42`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:40:9
++ --> $DIR/match_same_arms2.rs:37:9
+ |
+LL | 42 => foo(),
+ | ^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:39:9
++ --> $DIR/match_same_arms2.rs:44:9
+ |
+LL | None => 24, //~ ERROR match arms have same body
+ | ----^^^^^^
+ | |
+ | help: try merging the arm patterns: `None | Some(_)`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:62:9
++ --> $DIR/match_same_arms2.rs:43:9
+ |
+LL | Some(_) => 24,
+ | ^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:61:9
++ --> $DIR/match_same_arms2.rs:66:9
+ |
+LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body
+ | ---------------^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:67:9
++ --> $DIR/match_same_arms2.rs:65:9
+ |
+LL | (Some(a), None) => bar(a),
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:68:9
++ --> $DIR/match_same_arms2.rs:71:9
+ |
+LL | (Some(a), ..) => bar(a),
+ | -------------^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:101:9
++ --> $DIR/match_same_arms2.rs:72:9
+ |
+LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:102:9
++ --> $DIR/match_same_arms2.rs:105:9
+ |
+LL | (Ok(x), Some(_)) => println!("ok {}", x),
+ | ----------------^^^^^^^^^^^^^^^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:117:9
++ --> $DIR/match_same_arms2.rs:106:9
+ |
+LL | (Ok(_), Some(x)) => println!("ok {}", x),
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:116:9
++ --> $DIR/match_same_arms2.rs:121:9
+ |
+LL | Ok(_) => println!("ok"),
+ | -----^^^^^^^^^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `Ok(_) | Ok(3)`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:144:9
++ --> $DIR/match_same_arms2.rs:120:9
+ |
+LL | Ok(3) => println!("ok"),
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:141:9
++ --> $DIR/match_same_arms2.rs:148:9
+ |
+LL | 1 => {
+ | ^ help: try merging the arm patterns: `1 | 0`
+ | _________|
+ | |
+LL | | empty!(0);
+LL | | },
+ | |_________^
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:162:16
++ --> $DIR/match_same_arms2.rs:145:9
+ |
+LL | / 0 => {
+LL | | empty!(0);
+LL | | },
+ | |_________^
+
+error: match expression looks like `matches!` macro
- --> $DIR/match_same_arms2.rs:194:9
++ --> $DIR/match_same_arms2.rs:166:16
+ |
+LL | let _ans = match x {
+ | ________________^
+LL | | E::A => false,
+LL | | E::B => false,
+LL | | _ => true,
+LL | | };
+ | |_____^ help: try this: `!matches!(x, E::A | E::B)`
+ |
+ = note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:196:9
++ --> $DIR/match_same_arms2.rs:198:9
+ |
+LL | Foo::X(0) => 1,
+ | ---------^^^^^
+ | |
+ | help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:204:9
++ --> $DIR/match_same_arms2.rs:200:9
+ |
+LL | Foo::Z(_) => 1,
+ | ^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:202:9
++ --> $DIR/match_same_arms2.rs:208:9
+ |
+LL | Foo::Z(_) => 1,
+ | ---------^^^^^
+ | |
+ | help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)`
+ |
+ = help: or try changing either arm body
+note: other arm here
- --> $DIR/match_same_arms2.rs:227:9
++ --> $DIR/match_same_arms2.rs:206:9
+ |
+LL | Foo::X(0) => 1,
+ | ^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
- --> $DIR/match_same_arms2.rs:224:9
++ --> $DIR/match_same_arms2.rs:231:9
+ |
+LL | Some(Bar { y: 0, x: 5, .. }) => 1,
+ | ----------------------------^^^^^
+ | |
+ | help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })`
+ |
+ = help: or try changing either arm body
+note: other arm here
++ --> $DIR/match_same_arms2.rs:228:9
+ |
+LL | Some(Bar { x: 0, y: 5, .. }) => 1,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 12 previous errors
+
--- /dev/null
-
+// run-rustfix
- #![allow(unused_variables, clippy::toplevel_ref_arg)]
+#![warn(clippy::match_single_binding)]
++#![allow(unused_variables)]
++#![allow(clippy::toplevel_ref_arg, clippy::uninlined_format_args)]
+
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+fn coords() -> Point {
+ Point { x: 1, y: 2 }
+}
+
+macro_rules! foo {
+ ($param:expr) => {
+ match $param {
+ _ => println!("whatever"),
+ }
+ };
+}
+
+fn main() {
+ let a = 1;
+ let b = 2;
+ let c = 3;
+ // Lint
+ let (x, y, z) = (a, b, c);
+ {
+ println!("{} {} {}", x, y, z);
+ }
+ // Lint
+ let (x, y, z) = (a, b, c);
+ println!("{} {} {}", x, y, z);
+ // Ok
+ foo!(a);
+ // Ok
+ match a {
+ 2 => println!("2"),
+ _ => println!("Not 2"),
+ }
+ // Ok
+ let d = Some(5);
+ match d {
+ Some(d) => println!("{}", d),
+ _ => println!("None"),
+ }
+ // Lint
+ println!("whatever");
+ // Lint
+ {
+ let x = 29;
+ println!("x has a value of {}", x);
+ }
+ // Lint
+ {
+ let e = 5 * a;
+ if e >= 5 {
+ println!("e is superior to 5");
+ }
+ }
+ // Lint
+ let p = Point { x: 0, y: 7 };
+ let Point { x, y } = p;
+ println!("Coords: ({}, {})", x, y);
+ // Lint
+ let Point { x: x1, y: y1 } = p;
+ println!("Coords: ({}, {})", x1, y1);
+ // Lint
+ let x = 5;
+ let ref r = x;
+ println!("Got a reference to {}", r);
+ // Lint
+ let mut x = 5;
+ let ref mut mr = x;
+ println!("Got a mutable reference to {}", mr);
+ // Lint
+ let Point { x, y } = coords();
+ let product = x * y;
+ // Lint
+ let v = vec![Some(1), Some(2), Some(3), Some(4)];
+ #[allow(clippy::let_and_return)]
+ let _ = v
+ .iter()
+ .map(|i| {
+ let unwrapped = i.unwrap();
+ unwrapped
+ })
+ .collect::<Vec<u8>>();
+ // Ok
+ let x = 1;
+ match x {
+ #[cfg(disabled_feature)]
+ 0 => println!("Disabled branch"),
+ _ => println!("Enabled branch"),
+ }
+
+ // Ok
+ let x = 1;
+ let y = 1;
+ match match y {
+ 0 => 1,
+ _ => 2,
+ } {
+ #[cfg(disabled_feature)]
+ 0 => println!("Array index start"),
+ _ => println!("Not an array index start"),
+ }
+
+ // Lint
+ let x = 1;
+ println!("Not an array index start");
+}
+
+#[allow(dead_code)]
+fn issue_8723() {
+ let (mut val, idx) = ("a b", 1);
+
+ let (pre, suf) = val.split_at(idx);
+ val = {
+ println!("{}", pre);
+ suf
+ };
+
+ let _ = val;
+}
--- /dev/null
-
+// run-rustfix
- #![allow(unused_variables, clippy::toplevel_ref_arg)]
+#![warn(clippy::match_single_binding)]
++#![allow(unused_variables)]
++#![allow(clippy::toplevel_ref_arg, clippy::uninlined_format_args)]
+
+struct Point {
+ x: i32,
+ y: i32,
+}
+
+fn coords() -> Point {
+ Point { x: 1, y: 2 }
+}
+
+macro_rules! foo {
+ ($param:expr) => {
+ match $param {
+ _ => println!("whatever"),
+ }
+ };
+}
+
+fn main() {
+ let a = 1;
+ let b = 2;
+ let c = 3;
+ // Lint
+ match (a, b, c) {
+ (x, y, z) => {
+ println!("{} {} {}", x, y, z);
+ },
+ }
+ // Lint
+ match (a, b, c) {
+ (x, y, z) => println!("{} {} {}", x, y, z),
+ }
+ // Ok
+ foo!(a);
+ // Ok
+ match a {
+ 2 => println!("2"),
+ _ => println!("Not 2"),
+ }
+ // Ok
+ let d = Some(5);
+ match d {
+ Some(d) => println!("{}", d),
+ _ => println!("None"),
+ }
+ // Lint
+ match a {
+ _ => println!("whatever"),
+ }
+ // Lint
+ match a {
+ _ => {
+ let x = 29;
+ println!("x has a value of {}", x);
+ },
+ }
+ // Lint
+ match a {
+ _ => {
+ let e = 5 * a;
+ if e >= 5 {
+ println!("e is superior to 5");
+ }
+ },
+ }
+ // Lint
+ let p = Point { x: 0, y: 7 };
+ match p {
+ Point { x, y } => println!("Coords: ({}, {})", x, y),
+ }
+ // Lint
+ match p {
+ Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1),
+ }
+ // Lint
+ let x = 5;
+ match x {
+ ref r => println!("Got a reference to {}", r),
+ }
+ // Lint
+ let mut x = 5;
+ match x {
+ ref mut mr => println!("Got a mutable reference to {}", mr),
+ }
+ // Lint
+ let product = match coords() {
+ Point { x, y } => x * y,
+ };
+ // Lint
+ let v = vec![Some(1), Some(2), Some(3), Some(4)];
+ #[allow(clippy::let_and_return)]
+ let _ = v
+ .iter()
+ .map(|i| match i.unwrap() {
+ unwrapped => unwrapped,
+ })
+ .collect::<Vec<u8>>();
+ // Ok
+ let x = 1;
+ match x {
+ #[cfg(disabled_feature)]
+ 0 => println!("Disabled branch"),
+ _ => println!("Enabled branch"),
+ }
+
+ // Ok
+ let x = 1;
+ let y = 1;
+ match match y {
+ 0 => 1,
+ _ => 2,
+ } {
+ #[cfg(disabled_feature)]
+ 0 => println!("Array index start"),
+ _ => println!("Not an array index start"),
+ }
+
+ // Lint
+ let x = 1;
+ match x {
+ // =>
+ _ => println!("Not an array index start"),
+ }
+}
+
+#[allow(dead_code)]
+fn issue_8723() {
+ let (mut val, idx) = ("a b", 1);
+
+ val = match val.split_at(idx) {
+ (pre, suf) => {
+ println!("{}", pre);
+ suf
+ },
+ };
+
+ let _ = val;
+}
--- /dev/null
-
+// run-rustfix
+#![warn(clippy::match_single_binding)]
+#![allow(unused_variables)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ // Lint (additional curly braces needed, see #6572)
+ struct AppendIter<I>
+ where
+ I: Iterator,
+ {
+ inner: Option<(I, <I as Iterator>::Item)>,
+ }
+
+ #[allow(dead_code)]
+ fn size_hint<I: Iterator>(iter: &AppendIter<I>) -> (usize, Option<usize>) {
+ match &iter.inner {
+ Some((iter, _item)) => {
+ let (min, max) = iter.size_hint();
+ (min.saturating_add(1), max.and_then(|max| max.checked_add(1)))
+ },
+ None => (0, Some(0)),
+ }
+ }
+
+ // Lint (no additional curly braces needed)
+ let opt = Some((5, 2));
+ let get_tup = || -> (i32, i32) { (1, 2) };
+ match opt {
+ #[rustfmt::skip]
+ Some((first, _second)) => {
+ let (a, b) = get_tup();
+ println!("a {:?} and b {:?}", a, b);
+ },
+ None => println!("nothing"),
+ }
+
+ fn side_effects() {}
+
+ // Lint (scrutinee has side effects)
+ // issue #7094
+ side_effects();
+ println!("Side effects");
+
+ // Lint (scrutinee has side effects)
+ // issue #7094
+ let x = 1;
+ match x {
+ 0 => 1,
+ _ => 2,
+ };
+ println!("Single branch");
+}
--- /dev/null
-
+// run-rustfix
+#![warn(clippy::match_single_binding)]
+#![allow(unused_variables)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ // Lint (additional curly braces needed, see #6572)
+ struct AppendIter<I>
+ where
+ I: Iterator,
+ {
+ inner: Option<(I, <I as Iterator>::Item)>,
+ }
+
+ #[allow(dead_code)]
+ fn size_hint<I: Iterator>(iter: &AppendIter<I>) -> (usize, Option<usize>) {
+ match &iter.inner {
+ Some((iter, _item)) => match iter.size_hint() {
+ (min, max) => (min.saturating_add(1), max.and_then(|max| max.checked_add(1))),
+ },
+ None => (0, Some(0)),
+ }
+ }
+
+ // Lint (no additional curly braces needed)
+ let opt = Some((5, 2));
+ let get_tup = || -> (i32, i32) { (1, 2) };
+ match opt {
+ #[rustfmt::skip]
+ Some((first, _second)) => {
+ match get_tup() {
+ (a, b) => println!("a {:?} and b {:?}", a, b),
+ }
+ },
+ None => println!("nothing"),
+ }
+
+ fn side_effects() {}
+
+ // Lint (scrutinee has side effects)
+ // issue #7094
+ match side_effects() {
+ _ => println!("Side effects"),
+ }
+
+ // Lint (scrutinee has side effects)
+ // issue #7094
+ let x = 1;
+ match match x {
+ 0 => 1,
+ _ => 2,
+ } {
+ _ => println!("Single branch"),
+ }
+}
--- /dev/null
+#![warn(clippy::all)]
++#![allow(clippy::manual_clamp)]
+
+use std::cmp::max as my_max;
+use std::cmp::min as my_min;
+use std::cmp::{max, min};
+
+const LARGE: usize = 3;
+
+struct NotOrd(u64);
+
+impl NotOrd {
+ fn min(self, x: u64) -> NotOrd {
+ NotOrd(x)
+ }
+
+ fn max(self, x: u64) -> NotOrd {
+ NotOrd(x)
+ }
+}
+
+fn main() {
+ let x = 2usize;
+ min(1, max(3, x));
+ min(max(3, x), 1);
+ max(min(x, 1), 3);
+ max(3, min(x, 1));
+
+ my_max(3, my_min(x, 1));
+
+ min(3, max(1, x)); // ok, could be 1, 2 or 3 depending on x
+
+ min(1, max(LARGE, x)); // no error, we don't lookup consts here
+
+ let y = 2isize;
+ min(max(y, -1), 3);
+
+ let s = "Hello";
+ min("Apple", max("Zoo", s));
+ max(min(s, "Apple"), "Zoo");
+
+ max("Apple", min(s, "Zoo")); // ok
+
+ let f = 3f32;
+ x.min(1).max(3);
+ x.max(3).min(1);
+ f.max(3f32).min(1f32);
+
+ x.max(1).min(3); // ok
+ x.min(3).max(1); // ok
+ f.min(3f32).max(1f32); // ok
+
+ max(x.min(1), 3);
+ min(x.max(1), 3); // ok
+
+ s.max("Zoo").min("Apple");
+ s.min("Apple").max("Zoo");
+
+ s.min("Zoo").max("Apple"); // ok
+
+ let not_ord = NotOrd(1);
+ not_ord.min(1).max(3); // ok
+}
--- /dev/null
- --> $DIR/min_max.rs:23:5
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:24:5
++ --> $DIR/min_max.rs:24:5
+ |
+LL | min(1, max(3, x));
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::min-max` implied by `-D warnings`
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:25:5
++ --> $DIR/min_max.rs:25:5
+ |
+LL | min(max(3, x), 1);
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:26:5
++ --> $DIR/min_max.rs:26:5
+ |
+LL | max(min(x, 1), 3);
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:28:5
++ --> $DIR/min_max.rs:27:5
+ |
+LL | max(3, min(x, 1));
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:38:5
++ --> $DIR/min_max.rs:29:5
+ |
+LL | my_max(3, my_min(x, 1));
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:39:5
++ --> $DIR/min_max.rs:39:5
+ |
+LL | min("Apple", max("Zoo", s));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:44:5
++ --> $DIR/min_max.rs:40:5
+ |
+LL | max(min(s, "Apple"), "Zoo");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:45:5
++ --> $DIR/min_max.rs:45:5
+ |
+LL | x.min(1).max(3);
+ | ^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:46:5
++ --> $DIR/min_max.rs:46:5
+ |
+LL | x.max(3).min(1);
+ | ^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:52:5
++ --> $DIR/min_max.rs:47:5
+ |
+LL | f.max(3f32).min(1f32);
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:55:5
++ --> $DIR/min_max.rs:53:5
+ |
+LL | max(x.min(1), 3);
+ | ^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:56:5
++ --> $DIR/min_max.rs:56:5
+ |
+LL | s.max("Zoo").min("Apple");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
++ --> $DIR/min_max.rs:57:5
+ |
+LL | s.min("Apple").max("Zoo");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 13 previous errors
+
--- /dev/null
+#![allow(clippy::redundant_clone)]
+#![feature(custom_inner_attributes)]
+#![clippy::msrv = "1.0.0"]
+
+use std::ops::{Deref, RangeFrom};
+
+fn approx_const() {
+ let log2_10 = 3.321928094887362;
+ let log10_2 = 0.301029995663981;
+}
+
+fn cloned_instead_of_copied() {
+ let _ = [1].iter().cloned();
+}
+
+fn option_as_ref_deref() {
+ let mut opt = Some(String::from("123"));
+
+ let _ = opt.as_ref().map(String::as_str);
+ let _ = opt.as_ref().map(|x| x.as_str());
+ let _ = opt.as_mut().map(String::as_mut_str);
+ let _ = opt.as_mut().map(|x| x.as_mut_str());
+}
+
+fn match_like_matches() {
+ let _y = match Some(5) {
+ Some(0) => true,
+ _ => false,
+ };
+}
+
+fn match_same_arms() {
+ match (1, 2, 3) {
+ (1, .., 3) => 42,
+ (.., 3) => 42, //~ ERROR match arms have same body
+ _ => 0,
+ };
+}
+
+fn match_same_arms2() {
+ let _ = match Some(42) {
+ Some(_) => 24,
+ None => 24, //~ ERROR match arms have same body
+ };
+}
+
+pub fn manual_strip_msrv() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+}
+
+pub fn redundant_fieldnames() {
+ let start = 0;
+ let _ = RangeFrom { start: start };
+}
+
+pub fn redundant_static_lifetime() {
+ const VAR_ONE: &'static str = "Test constant #1";
+}
+
+pub fn checked_conversion() {
+ let value: i64 = 42;
+ let _ = value <= (u32::max_value() as i64) && value >= 0;
+ let _ = value <= (u32::MAX as i64) && value >= 0;
+}
+
+pub struct FromOverInto(String);
+
+impl Into<FromOverInto> for String {
+ fn into(self) -> FromOverInto {
+ FromOverInto(self)
+ }
+}
+
+pub fn filter_map_next() {
+ let a = ["1", "lol", "3", "NaN", "5"];
+
+ #[rustfmt::skip]
+ let _: Option<u32> = vec![1, 2, 3, 4, 5, 6]
+ .into_iter()
+ .filter_map(|x| {
+ if x == 2 {
+ Some(x * 2)
+ } else {
+ None
+ }
+ })
+ .next();
+}
+
+#[allow(clippy::no_effect)]
+#[allow(clippy::short_circuit_statement)]
+#[allow(clippy::unnecessary_operation)]
+pub fn manual_range_contains() {
+ let x = 5;
+ x >= 8 && x < 12;
+}
+
+pub fn use_self() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Foo {
+ Foo {}
+ }
+ fn test() -> Foo {
+ Foo::new()
+ }
+ }
+}
+
+fn replace_with_default() {
+ let mut s = String::from("foo");
+ let _ = std::mem::replace(&mut s, String::default());
+}
+
+fn map_unwrap_or() {
+ let opt = Some(1);
+
+ // Check for `option.map(_).unwrap_or(_)` use.
+ // Single line case.
+ let _ = opt
+ .map(|x| x + 1)
+ // Should lint even though this call is on a separate line.
+ .unwrap_or(0);
+}
+
+// Could be const
+fn missing_const_for_fn() -> i32 {
+ 1
+}
+
+fn unnest_or_patterns() {
+ struct TS(u8, u8);
+ if let TS(0, x) | TS(1, x) = TS(0, 0) {}
+}
+
+#[cfg_attr(rustfmt, rustfmt_skip)]
+fn deprecated_cfg_attr() {}
+
+#[warn(clippy::cast_lossless)]
+fn int_from_bool() -> u8 {
+ true as u8
+}
+
+fn err_expect() {
+ let x: Result<u32, &str> = Ok(10);
+ x.err().expect("Testing expect_err");
+}
+
+fn cast_abs_to_unsigned() {
+ let x: i32 = 10;
+ assert_eq!(10u32, x.abs() as u32);
+}
+
+fn manual_rem_euclid() {
+ let x: i32 = 10;
+ let _: i32 = ((x % 4) + 4) % 4;
+}
+
++fn manual_clamp() {
++ let (input, min, max) = (0, -1, 2);
++ let _ = if input < min {
++ min
++ } else if input > max {
++ max
++ } else {
++ input
++ };
++}
++
+fn main() {
+ filter_map_next();
+ checked_conversion();
+ redundant_fieldnames();
+ redundant_static_lifetime();
+ option_as_ref_deref();
+ match_like_matches();
+ match_same_arms();
+ match_same_arms2();
+ manual_strip_msrv();
+ manual_range_contains();
+ use_self();
+ replace_with_default();
+ map_unwrap_or();
+ missing_const_for_fn();
+ unnest_or_patterns();
+ int_from_bool();
+ err_expect();
+ cast_abs_to_unsigned();
+ manual_rem_euclid();
++ manual_clamp();
+}
+
+mod just_under_msrv {
+ #![feature(custom_inner_attributes)]
+ #![clippy::msrv = "1.44.0"]
+
+ fn main() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+ }
+}
+
+mod meets_msrv {
+ #![feature(custom_inner_attributes)]
+ #![clippy::msrv = "1.45.0"]
+
+ fn main() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+ }
+}
+
+mod just_above_msrv {
+ #![feature(custom_inner_attributes)]
+ #![clippy::msrv = "1.46.0"]
+
+ fn main() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+ }
+}
+
+mod const_rem_euclid {
+ #![feature(custom_inner_attributes)]
+ #![clippy::msrv = "1.50.0"]
+
+ pub const fn const_rem_euclid_4(num: i32) -> i32 {
+ ((num % 4) + 4) % 4
+ }
+}
--- /dev/null
- --> $DIR/min_rust_version_attr.rs:204:24
+error: stripping a prefix manually
- --> $DIR/min_rust_version_attr.rs:203:9
++ --> $DIR/min_rust_version_attr.rs:216:24
+ |
+LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+note: the prefix was tested here
- --> $DIR/min_rust_version_attr.rs:216:24
++ --> $DIR/min_rust_version_attr.rs:215:9
+ |
+LL | if s.starts_with("hello, ") {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ = note: `-D clippy::manual-strip` implied by `-D warnings`
+help: try using the `strip_prefix` method
+ |
+LL ~ if let Some(<stripped>) = s.strip_prefix("hello, ") {
+LL ~ assert_eq!(<stripped>.to_uppercase(), "WORLD!");
+ |
+
+error: stripping a prefix manually
- --> $DIR/min_rust_version_attr.rs:215:9
++ --> $DIR/min_rust_version_attr.rs:228:24
+ |
+LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+note: the prefix was tested here
++ --> $DIR/min_rust_version_attr.rs:227:9
+ |
+LL | if s.starts_with("hello, ") {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: try using the `strip_prefix` method
+ |
+LL ~ if let Some(<stripped>) = s.strip_prefix("hello, ") {
+LL ~ assert_eq!(<stripped>.to_uppercase(), "WORLD!");
+ |
+
+error: aborting due to 2 previous errors
+
--- /dev/null
-
- #![allow(unused, clippy::no_effect, clippy::unnecessary_operation)]
+// 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!();
+}
--- /dev/null
-
+// run-rustfix
- #[allow(unused_variables, clippy::unnecessary_mut_passed)]
+#![feature(custom_inner_attributes, lint_reasons)]
+
+#[warn(clippy::all, clippy::needless_borrow)]
++#[allow(unused_variables)]
++#[allow(clippy::uninlined_format_args, clippy::unnecessary_mut_passed)]
+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("");
+
+ 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);
+ }
+ 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);
++ }
++}
--- /dev/null
-
+// run-rustfix
- #[allow(unused_variables, clippy::unnecessary_mut_passed)]
+#![feature(custom_inner_attributes, lint_reasons)]
+
+#[warn(clippy::all, clippy::needless_borrow)]
++#[allow(unused_variables)]
++#[allow(clippy::uninlined_format_args, clippy::unnecessary_mut_passed)]
+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(&"");
+
+ 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);
+ }
+ 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);
++ }
++}
--- /dev/null
- #[warn(clippy::needless_borrowed_reference)]
- #[allow(unused_variables)]
- fn main() {
+// run-rustfix
+
- // ^ should be linted
++#![warn(clippy::needless_borrowed_reference)]
++#![allow(unused, clippy::needless_borrow)]
++
++fn main() {}
++
++fn should_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
+ let mut v = Vec::<String>::new();
+ let _ = v.iter_mut().filter(|a| a.is_empty());
- if let Some(&ref v) = thingy {
- // ^ should be linted
- }
+
+ let var = 3;
+ let thingy = Some(&var);
- #[allow(dead_code)]
++ 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 {}
++}
++
++fn should_not_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
++ if let [ref a] = slice {}
++ if let &[ref a, b] = slice {}
++ if let &[ref a, .., b] = slice {}
++
++ // must not be removed as variables must be bound consistently across | patterns
++ if let (&[ref a], _) | ([], ref a) = (slice_of_refs, &1u8) {}
+
+ 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;
+ }
+}
+
- #[allow(unused_variables)]
- #[allow(dead_code)]
+enum Animal {
+ Cat(u64),
+ Dog(u64),
+}
+
- (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref'
+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(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted
+ }
+}
--- /dev/null
- #[warn(clippy::needless_borrowed_reference)]
- #[allow(unused_variables)]
- fn main() {
+// run-rustfix
+
- // ^ should be linted
++#![warn(clippy::needless_borrowed_reference)]
++#![allow(unused, clippy::needless_borrow)]
++
++fn main() {}
++
++fn should_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
+ let mut v = Vec::<String>::new();
+ let _ = v.iter_mut().filter(|&ref a| a.is_empty());
- if let Some(&ref v) = thingy {
- // ^ should be linted
- }
+
+ let var = 3;
+ let thingy = Some(&var);
- #[allow(dead_code)]
++ 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 {}
++}
++
++fn should_not_lint(array: [u8; 4], slice: &[u8], slice_of_refs: &[&u8], vec: Vec<u8>) {
++ if let [ref a] = slice {}
++ if let &[ref a, b] = slice {}
++ if let &[ref a, .., b] = slice {}
++
++ // must not be removed as variables must be bound consistently across | patterns
++ if let (&[ref a], _) | ([], ref a) = (slice_of_refs, &1u8) {}
+
+ 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;
+ }
+}
+
- #[allow(unused_variables)]
- #[allow(dead_code)]
+enum Animal {
+ Cat(u64),
+ Dog(u64),
+}
+
- (&Animal::Cat(v), &ref k) | (&ref k, &Animal::Cat(v)) => (), // lifetime mismatch error if there is no '&ref'
+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(ref a), &Animal::Dog(_)) => (), // ^ should **not** be linted
+ }
+}
--- /dev/null
- error: this pattern takes a reference on something that is being de-referenced
- --> $DIR/needless_borrowed_ref.rs:7:34
++error: this pattern takes a reference on something that is being dereferenced
++ --> $DIR/needless_borrowed_ref.rs:10:34
+ |
+LL | let _ = v.iter_mut().filter(|&ref a| a.is_empty());
- | ^^^^^^ help: try removing the `&ref` part and just keep: `a`
++ | ^^^^^^
+ |
+ = 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:14: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:16: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:18: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:19: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:21: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:22: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:24: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:25: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
++ |
++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: aborting due to previous error
++error: aborting due to 10 previous errors
+
--- /dev/null
++#![allow(clippy::uninlined_format_args)]
++
+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:5:39
+error: avoid using `collect()` when not needed
- --> $DIR/needless_collect_indirect.rs:7:38
++ --> $DIR/needless_collect_indirect.rs:7: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:9:40
++ --> $DIR/needless_collect_indirect.rs:9: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:11:43
++ --> $DIR/needless_collect_indirect.rs:11: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:23:48
++ --> $DIR/needless_collect_indirect.rs:13: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:52:51
++ --> $DIR/needless_collect_indirect.rs:25: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:57:55
++ --> $DIR/needless_collect_indirect.rs:54: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:62:57
++ --> $DIR/needless_collect_indirect.rs:59: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:67:57
++ --> $DIR/needless_collect_indirect.rs:64: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:127:59
++ --> $DIR/needless_collect_indirect.rs:69: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:152:59
++ --> $DIR/needless_collect_indirect.rs:129: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:181:63
++ --> $DIR/needless_collect_indirect.rs:154: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:217:59
++ --> $DIR/needless_collect_indirect.rs:183: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:242:26
++ --> $DIR/needless_collect_indirect.rs:219: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:264:30
++ --> $DIR/needless_collect_indirect.rs:244: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:286:30
++ --> $DIR/needless_collect_indirect.rs:266: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:288: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
+#![warn(clippy::needless_continue)]
++#![allow(clippy::uninlined_format_args)]
+
+macro_rules! zero {
+ ($x:expr) => {
+ $x == 0
+ };
+}
+
+macro_rules! nonzero {
+ ($x:expr) => {
+ !zero!($x)
+ };
+}
+
+#[allow(clippy::nonminimal_bool)]
+fn main() {
+ let mut i = 1;
+ while i < 10 {
+ i += 1;
+
+ if i % 2 == 0 && i % 3 == 0 {
+ println!("{}", i);
+ println!("{}", i + 1);
+ if i % 5 == 0 {
+ println!("{}", i + 2);
+ }
+ let i = 0;
+ println!("bar {} ", i);
+ } else {
+ continue;
+ }
+
+ println!("bleh");
+ {
+ println!("blah");
+ }
+
+ // some comments that also should ideally be included in the
+ // output of the lint suggestion if possible.
+ if !(!(i == 2) || !(i == 5)) {
+ println!("lama");
+ }
+
+ if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 {
+ continue;
+ } else {
+ println!("Blabber");
+ println!("Jabber");
+ }
+
+ println!("bleh");
+ }
+}
+
+fn simple_loop() {
+ loop {
+ continue; // should lint here
+ }
+}
+
+fn simple_loop2() {
+ loop {
+ println!("bleh");
+ continue; // should lint here
+ }
+}
+
+#[rustfmt::skip]
+fn simple_loop3() {
+ loop {
+ continue // should lint here
+ }
+}
+
+#[rustfmt::skip]
+fn simple_loop4() {
+ loop {
+ println!("bleh");
+ continue // should lint here
+ }
+}
+
+mod issue_2329 {
+ fn condition() -> bool {
+ unimplemented!()
+ }
+ fn update_condition() {}
+
+ // only the outer loop has a label
+ fn foo() {
+ 'outer: loop {
+ println!("Entry");
+ while condition() {
+ update_condition();
+ if condition() {
+ println!("foo-1");
+ } else {
+ continue 'outer; // should not lint here
+ }
+ println!("foo-2");
+
+ update_condition();
+ if condition() {
+ continue 'outer; // should not lint here
+ } else {
+ println!("foo-3");
+ }
+ println!("foo-4");
+ }
+ }
+ }
+
+ // both loops have labels
+ fn bar() {
+ 'outer: loop {
+ println!("Entry");
+ 'inner: while condition() {
+ update_condition();
+ if condition() {
+ println!("bar-1");
+ } else {
+ continue 'outer; // should not lint here
+ }
+ println!("bar-2");
+
+ update_condition();
+ if condition() {
+ println!("bar-3");
+ } else {
+ continue 'inner; // should lint here
+ }
+ println!("bar-4");
+
+ update_condition();
+ if condition() {
+ continue; // should lint here
+ } else {
+ println!("bar-5");
+ }
+ println!("bar-6");
+ }
+ }
+ }
+}
--- /dev/null
- --> $DIR/needless_continue.rs:29:16
+error: this `else` block is redundant
- --> $DIR/needless_continue.rs:44:9
++ --> $DIR/needless_continue.rs:30:16
+ |
+LL | } else {
+ | ________________^
+LL | | continue;
+LL | | }
+ | |_________^
+ |
+ = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block
+ if i % 2 == 0 && i % 3 == 0 {
+ println!("{}", i);
+ println!("{}", i + 1);
+ if i % 5 == 0 {
+ println!("{}", i + 2);
+ }
+ let i = 0;
+ println!("bar {} ", i);
+ // merged code follows:
+ println!("bleh");
+ {
+ println!("blah");
+ }
+ if !(!(i == 2) || !(i == 5)) {
+ println!("lama");
+ }
+ if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 {
+ continue;
+ } else {
+ println!("Blabber");
+ println!("Jabber");
+ }
+ println!("bleh");
+ }
+ = note: `-D clippy::needless-continue` implied by `-D warnings`
+
+error: there is no need for an explicit `else` block for this `if` expression
- --> $DIR/needless_continue.rs:57:9
++ --> $DIR/needless_continue.rs:45:9
+ |
+LL | / if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 {
+LL | | continue;
+LL | | } else {
+LL | | println!("Blabber");
+LL | | println!("Jabber");
+LL | | }
+ | |_________^
+ |
+ = help: consider dropping the `else` clause
+ if (zero!(i % 2) || nonzero!(i % 5)) && i % 3 != 0 {
+ continue;
+ }
+ {
+ println!("Blabber");
+ println!("Jabber");
+ }
+
+error: this `continue` expression is redundant
- --> $DIR/needless_continue.rs:64:9
++ --> $DIR/needless_continue.rs:58:9
+ |
+LL | continue; // should lint here
+ | ^^^^^^^^^
+ |
+ = help: consider dropping the `continue` expression
+
+error: this `continue` expression is redundant
- --> $DIR/needless_continue.rs:71:9
++ --> $DIR/needless_continue.rs:65:9
+ |
+LL | continue; // should lint here
+ | ^^^^^^^^^
+ |
+ = help: consider dropping the `continue` expression
+
+error: this `continue` expression is redundant
- --> $DIR/needless_continue.rs:79:9
++ --> $DIR/needless_continue.rs:72:9
+ |
+LL | continue // should lint here
+ | ^^^^^^^^
+ |
+ = help: consider dropping the `continue` expression
+
+error: this `continue` expression is redundant
- --> $DIR/needless_continue.rs:129:24
++ --> $DIR/needless_continue.rs:80:9
+ |
+LL | continue // should lint here
+ | ^^^^^^^^
+ |
+ = help: consider dropping the `continue` expression
+
+error: this `else` block is redundant
- --> $DIR/needless_continue.rs:135:17
++ --> $DIR/needless_continue.rs:130:24
+ |
+LL | } else {
+ | ________________________^
+LL | | continue 'inner; // should lint here
+LL | | }
+ | |_________________^
+ |
+ = help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block
+ if condition() {
+ println!("bar-3");
+ // merged code follows:
+ println!("bar-4");
+ update_condition();
+ if condition() {
+ continue; // should lint here
+ } else {
+ println!("bar-5");
+ }
+ println!("bar-6");
+ }
+
+error: there is no need for an explicit `else` block for this `if` expression
++ --> $DIR/needless_continue.rs:136:17
+ |
+LL | / if condition() {
+LL | | continue; // should lint here
+LL | | } else {
+LL | | println!("bar-5");
+LL | | }
+ | |_________________^
+ |
+ = help: consider dropping the `else` clause
+ if condition() {
+ continue; // should lint here
+ }
+ {
+ println!("bar-5");
+ }
+
+error: aborting due to 8 previous errors
+
--- /dev/null
- unused,
- clippy::needless_return,
+// run-rustfix
+#![warn(clippy::needless_for_each)]
++#![allow(unused)]
+#![allow(
- clippy::let_unit_value
++ clippy::let_unit_value,
+ clippy::match_single_binding,
++ clippy::needless_return,
++ clippy::uninlined_format_args
+)]
+
+use std::collections::HashMap;
+
+fn should_lint() {
+ let v: Vec<i32> = Vec::new();
+ let mut acc = 0;
+ for elem in v.iter() {
+ acc += elem;
+ }
+ for elem in v.into_iter() {
+ acc += elem;
+ }
+
+ for elem in [1, 2, 3].iter() {
+ acc += elem;
+ }
+
+ let mut hash_map: HashMap<i32, i32> = HashMap::new();
+ for (k, v) in hash_map.iter() {
+ acc += k + v;
+ }
+ for (k, v) in hash_map.iter_mut() {
+ acc += *k + *v;
+ }
+ for k in hash_map.keys() {
+ acc += k;
+ }
+ for v in hash_map.values() {
+ acc += v;
+ }
+
+ fn my_vec() -> Vec<i32> {
+ Vec::new()
+ }
+ for elem in my_vec().iter() {
+ acc += elem;
+ }
+}
+
+fn should_not_lint() {
+ let v: Vec<i32> = Vec::new();
+ let mut acc = 0;
+
+ // `for_each` argument is not closure.
+ fn print(x: &i32) {
+ println!("{}", x);
+ }
+ v.iter().for_each(print);
+
+ // User defined type.
+ struct MyStruct {
+ v: Vec<i32>,
+ }
+ impl MyStruct {
+ fn iter(&self) -> impl Iterator<Item = &i32> {
+ self.v.iter()
+ }
+ }
+ let s = MyStruct { v: Vec::new() };
+ s.iter().for_each(|elem| {
+ acc += elem;
+ });
+
+ // `for_each` follows long iterator chain.
+ v.iter().chain(v.iter()).for_each(|v| {
+ acc += v;
+ });
+ v.as_slice().iter().for_each(|v| {
+ acc += v;
+ });
+ s.v.iter().for_each(|v| {
+ acc += v;
+ });
+
+ // `return` is used in `Loop` of the closure.
+ v.iter().for_each(|v| {
+ for i in 0..*v {
+ if i == 10 {
+ return;
+ } else {
+ println!("{}", v);
+ }
+ }
+ if *v == 20 {
+ return;
+ } else {
+ println!("{}", v);
+ }
+ });
+
+ // Previously transformed iterator variable.
+ let it = v.iter();
+ it.chain(v.iter()).for_each(|elem| {
+ acc += elem;
+ });
+
+ // `for_each` is not directly in a statement.
+ match 1 {
+ _ => v.iter().for_each(|elem| {
+ acc += elem;
+ }),
+ }
+
+ // `for_each` is in a let bingind.
+ let _ = v.iter().for_each(|elem| {
+ acc += elem;
+ });
+}
+
+fn main() {}
--- /dev/null
- unused,
- clippy::needless_return,
+// run-rustfix
+#![warn(clippy::needless_for_each)]
++#![allow(unused)]
+#![allow(
- clippy::let_unit_value
++ clippy::let_unit_value,
+ clippy::match_single_binding,
++ clippy::needless_return,
++ clippy::uninlined_format_args
+)]
+
+use std::collections::HashMap;
+
+fn should_lint() {
+ let v: Vec<i32> = Vec::new();
+ let mut acc = 0;
+ v.iter().for_each(|elem| {
+ acc += elem;
+ });
+ v.into_iter().for_each(|elem| {
+ acc += elem;
+ });
+
+ [1, 2, 3].iter().for_each(|elem| {
+ acc += elem;
+ });
+
+ let mut hash_map: HashMap<i32, i32> = HashMap::new();
+ hash_map.iter().for_each(|(k, v)| {
+ acc += k + v;
+ });
+ hash_map.iter_mut().for_each(|(k, v)| {
+ acc += *k + *v;
+ });
+ hash_map.keys().for_each(|k| {
+ acc += k;
+ });
+ hash_map.values().for_each(|v| {
+ acc += v;
+ });
+
+ fn my_vec() -> Vec<i32> {
+ Vec::new()
+ }
+ my_vec().iter().for_each(|elem| {
+ acc += elem;
+ });
+}
+
+fn should_not_lint() {
+ let v: Vec<i32> = Vec::new();
+ let mut acc = 0;
+
+ // `for_each` argument is not closure.
+ fn print(x: &i32) {
+ println!("{}", x);
+ }
+ v.iter().for_each(print);
+
+ // User defined type.
+ struct MyStruct {
+ v: Vec<i32>,
+ }
+ impl MyStruct {
+ fn iter(&self) -> impl Iterator<Item = &i32> {
+ self.v.iter()
+ }
+ }
+ let s = MyStruct { v: Vec::new() };
+ s.iter().for_each(|elem| {
+ acc += elem;
+ });
+
+ // `for_each` follows long iterator chain.
+ v.iter().chain(v.iter()).for_each(|v| {
+ acc += v;
+ });
+ v.as_slice().iter().for_each(|v| {
+ acc += v;
+ });
+ s.v.iter().for_each(|v| {
+ acc += v;
+ });
+
+ // `return` is used in `Loop` of the closure.
+ v.iter().for_each(|v| {
+ for i in 0..*v {
+ if i == 10 {
+ return;
+ } else {
+ println!("{}", v);
+ }
+ }
+ if *v == 20 {
+ return;
+ } else {
+ println!("{}", v);
+ }
+ });
+
+ // Previously transformed iterator variable.
+ let it = v.iter();
+ it.chain(v.iter()).for_each(|elem| {
+ acc += elem;
+ });
+
+ // `for_each` is not directly in a statement.
+ match 1 {
+ _ => v.iter().for_each(|elem| {
+ acc += elem;
+ }),
+ }
+
+ // `for_each` is in a let bingind.
+ let _ = v.iter().for_each(|elem| {
+ acc += elem;
+ });
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/needless_for_each_fixable.rs:15:5
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:18:5
++ --> $DIR/needless_for_each_fixable.rs:16:5
+ |
+LL | / v.iter().for_each(|elem| {
+LL | | acc += elem;
+LL | | });
+ | |_______^
+ |
+ = note: `-D clippy::needless-for-each` implied by `-D warnings`
+help: try
+ |
+LL ~ for elem in v.iter() {
+LL + acc += elem;
+LL + }
+ |
+
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:22:5
++ --> $DIR/needless_for_each_fixable.rs:19:5
+ |
+LL | / v.into_iter().for_each(|elem| {
+LL | | acc += elem;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for elem in v.into_iter() {
+LL + acc += elem;
+LL + }
+ |
+
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:27:5
++ --> $DIR/needless_for_each_fixable.rs:23:5
+ |
+LL | / [1, 2, 3].iter().for_each(|elem| {
+LL | | acc += elem;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for elem in [1, 2, 3].iter() {
+LL + acc += elem;
+LL + }
+ |
+
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:30:5
++ --> $DIR/needless_for_each_fixable.rs:28:5
+ |
+LL | / hash_map.iter().for_each(|(k, v)| {
+LL | | acc += k + v;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for (k, v) in hash_map.iter() {
+LL + acc += k + v;
+LL + }
+ |
+
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:33:5
++ --> $DIR/needless_for_each_fixable.rs:31:5
+ |
+LL | / hash_map.iter_mut().for_each(|(k, v)| {
+LL | | acc += *k + *v;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for (k, v) in hash_map.iter_mut() {
+LL + acc += *k + *v;
+LL + }
+ |
+
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:36:5
++ --> $DIR/needless_for_each_fixable.rs:34:5
+ |
+LL | / hash_map.keys().for_each(|k| {
+LL | | acc += k;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for k in hash_map.keys() {
+LL + acc += k;
+LL + }
+ |
+
+error: needless use of `for_each`
- --> $DIR/needless_for_each_fixable.rs:43:5
++ --> $DIR/needless_for_each_fixable.rs:37:5
+ |
+LL | / hash_map.values().for_each(|v| {
+LL | | acc += v;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for v in hash_map.values() {
+LL + acc += v;
+LL + }
+ |
+
+error: needless use of `for_each`
++ --> $DIR/needless_for_each_fixable.rs:44:5
+ |
+LL | / my_vec().iter().for_each(|elem| {
+LL | | acc += elem;
+LL | | });
+ | |_______^
+ |
+help: try
+ |
+LL ~ for elem in my_vec().iter() {
+LL + acc += elem;
+LL + }
+ |
+
+error: aborting due to 8 previous errors
+
--- /dev/null
- #![allow(clippy::needless_return)]
+#![warn(clippy::needless_for_each)]
++#![allow(clippy::needless_return, clippy::uninlined_format_args)]
+
+fn main() {
+ let v: Vec<i32> = Vec::new();
+ // This is unfixable because the closure includes `return`.
+ v.iter().for_each(|v| {
+ if *v == 10 {
+ return;
+ } else {
+ println!("{}", v);
+ }
+ });
+}
--- /dev/null
- unused,
+// run-rustfix
+#![feature(let_chains)]
++#![allow(unused)]
+#![allow(
- clippy::nonminimal_bool
+ clippy::assign_op_pattern,
+ clippy::blocks_in_if_conditions,
+ clippy::let_and_return,
+ clippy::let_unit_value,
++ clippy::nonminimal_bool,
++ clippy::uninlined_format_args
+)]
+
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::rc::Rc;
+
+struct SignificantDrop;
+impl std::ops::Drop for SignificantDrop {
+ fn drop(&mut self) {
+ println!("dropped");
+ }
+}
+
+fn simple() {
+
+ let a = "zero";
+
+
+
+ let b = 1;
+ let c = 2;
+
+
+ let d: usize = 1;
+
+
+ let e = format!("{}", d);
+}
+
+fn main() {
+
+ let n = 1;
+ let a = match n {
+ 1 => "one",
+ _ => {
+ "two"
+ },
+ };
+
+
+ let b = if n == 3 {
+ "four"
+ } else {
+ "five"
+ };
+
+
+ let d = if true {
+ let temp = 5;
+ temp
+ } else {
+ 15
+ };
+
+
+ let e = if true {
+ format!("{} {}", a, b)
+ } else {
+ format!("{}", n)
+ };
+
+
+ let f = match 1 {
+ 1 => "three",
+ _ => return,
+ }; // has semi
+
+
+ let g: usize = if true {
+ 5
+ } else {
+ panic!();
+ };
+
+ // Drop order only matters if both are significant
+
+ let y = SignificantDrop;
+ let x = 1;
+
+
+ let y = 1;
+ let x = SignificantDrop;
+
+
+ // types that should be considered insignificant
+ let y = 1;
+ let y = "2";
+ let y = String::new();
+ let y = vec![3.0];
+ let y = HashMap::<usize, usize>::new();
+ let y = BTreeMap::<usize, usize>::new();
+ let y = HashSet::<usize>::new();
+ let y = BTreeSet::<usize>::new();
+ let y = Box::new(4);
+ let x = SignificantDrop;
+}
+
+async fn in_async() -> &'static str {
+ async fn f() -> &'static str {
+ "one"
+ }
+
+
+ let n = 1;
+ let a = match n {
+ 1 => f().await,
+ _ => {
+ "two"
+ },
+ };
+
+ a
+}
+
+const fn in_const() -> &'static str {
+ const fn f() -> &'static str {
+ "one"
+ }
+
+
+ let n = 1;
+ let a = match n {
+ 1 => f(),
+ _ => {
+ "two"
+ },
+ };
+
+ a
+}
+
+fn does_not_lint() {
+ let z;
+ if false {
+ z = 1;
+ }
+
+ let x;
+ let y;
+ if true {
+ x = 1;
+ } else {
+ y = 1;
+ }
+
+ let mut x;
+ if true {
+ x = 5;
+ x = 10 / x;
+ } else {
+ x = 2;
+ }
+
+ let x;
+ let _ = match 1 {
+ 1 => x = 10,
+ _ => x = 20,
+ };
+
+ // using tuples would be possible, but not always preferable
+ let x;
+ let y;
+ if true {
+ x = 1;
+ y = 2;
+ } else {
+ x = 3;
+ y = 4;
+ }
+
+ // could match with a smarter heuristic to avoid multiple assignments
+ let x;
+ if true {
+ let mut y = 5;
+ y = 6;
+ x = y;
+ } else {
+ x = 2;
+ }
+
+ let (x, y);
+ if true {
+ x = 1;
+ } else {
+ x = 2;
+ }
+ y = 3;
+
+ macro_rules! assign {
+ ($i:ident) => {
+ $i = 1;
+ };
+ }
+ let x;
+ assign!(x);
+
+ let x;
+ if true {
+ assign!(x);
+ } else {
+ x = 2;
+ }
+
+ macro_rules! in_macro {
+ () => {
+ let x;
+ x = 1;
+
+ let x;
+ if true {
+ x = 1;
+ } else {
+ x = 2;
+ }
+ };
+ }
+ in_macro!();
+
+ // ignore if-lets - https://github.com/rust-lang/rust-clippy/issues/8613
+ let x;
+ if let Some(n) = Some("v") {
+ x = 1;
+ } else {
+ x = 2;
+ }
+
+ let x;
+ if true && let Some(n) = Some("let chains too") {
+ x = 1;
+ } else {
+ x = 2;
+ }
+
+ // ignore mut bindings
+ // https://github.com/shepmaster/twox-hash/blob/b169c16d86eb8ea4a296b0acb9d00ca7e3c3005f/src/sixty_four.rs#L88-L93
+ // https://github.com/dtolnay/thiserror/blob/21c26903e29cb92ba1a7ff11e82ae2001646b60d/tests/test_generics.rs#L91-L100
+ let mut x: usize;
+ x = 1;
+ x = 2;
+ x = 3;
+
+ // should not move the declaration if `x` has a significant drop, and there
+ // is another binding with a significant drop between it and the first usage
+ let x;
+ let y = SignificantDrop;
+ x = SignificantDrop;
+}
+
+#[rustfmt::skip]
+fn issue8911() -> u32 {
+ let x;
+ match 1 {
+ _ if { x = 1; false } => return 1,
+ _ => return 2,
+ }
+
+ let x;
+ if { x = 1; true } {
+ return 1;
+ } else {
+ return 2;
+ }
+
+ 3
+}
--- /dev/null
- unused,
+// run-rustfix
+#![feature(let_chains)]
++#![allow(unused)]
+#![allow(
- clippy::nonminimal_bool
+ clippy::assign_op_pattern,
+ clippy::blocks_in_if_conditions,
+ clippy::let_and_return,
+ clippy::let_unit_value,
++ clippy::nonminimal_bool,
++ clippy::uninlined_format_args
+)]
+
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::rc::Rc;
+
+struct SignificantDrop;
+impl std::ops::Drop for SignificantDrop {
+ fn drop(&mut self) {
+ println!("dropped");
+ }
+}
+
+fn simple() {
+ let a;
+ a = "zero";
+
+ let b;
+ let c;
+ b = 1;
+ c = 2;
+
+ let d: usize;
+ d = 1;
+
+ let e;
+ e = format!("{}", d);
+}
+
+fn main() {
+ let a;
+ let n = 1;
+ match n {
+ 1 => a = "one",
+ _ => {
+ a = "two";
+ },
+ }
+
+ let b;
+ if n == 3 {
+ b = "four";
+ } else {
+ b = "five"
+ }
+
+ let d;
+ if true {
+ let temp = 5;
+ d = temp;
+ } else {
+ d = 15;
+ }
+
+ let e;
+ if true {
+ e = format!("{} {}", a, b);
+ } else {
+ e = format!("{}", n);
+ }
+
+ let f;
+ match 1 {
+ 1 => f = "three",
+ _ => return,
+ }; // has semi
+
+ let g: usize;
+ if true {
+ g = 5;
+ } else {
+ panic!();
+ }
+
+ // Drop order only matters if both are significant
+ let x;
+ let y = SignificantDrop;
+ x = 1;
+
+ let x;
+ let y = 1;
+ x = SignificantDrop;
+
+ let x;
+ // types that should be considered insignificant
+ let y = 1;
+ let y = "2";
+ let y = String::new();
+ let y = vec![3.0];
+ let y = HashMap::<usize, usize>::new();
+ let y = BTreeMap::<usize, usize>::new();
+ let y = HashSet::<usize>::new();
+ let y = BTreeSet::<usize>::new();
+ let y = Box::new(4);
+ x = SignificantDrop;
+}
+
+async fn in_async() -> &'static str {
+ async fn f() -> &'static str {
+ "one"
+ }
+
+ let a;
+ let n = 1;
+ match n {
+ 1 => a = f().await,
+ _ => {
+ a = "two";
+ },
+ }
+
+ a
+}
+
+const fn in_const() -> &'static str {
+ const fn f() -> &'static str {
+ "one"
+ }
+
+ let a;
+ let n = 1;
+ match n {
+ 1 => a = f(),
+ _ => {
+ a = "two";
+ },
+ }
+
+ a
+}
+
+fn does_not_lint() {
+ let z;
+ if false {
+ z = 1;
+ }
+
+ let x;
+ let y;
+ if true {
+ x = 1;
+ } else {
+ y = 1;
+ }
+
+ let mut x;
+ if true {
+ x = 5;
+ x = 10 / x;
+ } else {
+ x = 2;
+ }
+
+ let x;
+ let _ = match 1 {
+ 1 => x = 10,
+ _ => x = 20,
+ };
+
+ // using tuples would be possible, but not always preferable
+ let x;
+ let y;
+ if true {
+ x = 1;
+ y = 2;
+ } else {
+ x = 3;
+ y = 4;
+ }
+
+ // could match with a smarter heuristic to avoid multiple assignments
+ let x;
+ if true {
+ let mut y = 5;
+ y = 6;
+ x = y;
+ } else {
+ x = 2;
+ }
+
+ let (x, y);
+ if true {
+ x = 1;
+ } else {
+ x = 2;
+ }
+ y = 3;
+
+ macro_rules! assign {
+ ($i:ident) => {
+ $i = 1;
+ };
+ }
+ let x;
+ assign!(x);
+
+ let x;
+ if true {
+ assign!(x);
+ } else {
+ x = 2;
+ }
+
+ macro_rules! in_macro {
+ () => {
+ let x;
+ x = 1;
+
+ let x;
+ if true {
+ x = 1;
+ } else {
+ x = 2;
+ }
+ };
+ }
+ in_macro!();
+
+ // ignore if-lets - https://github.com/rust-lang/rust-clippy/issues/8613
+ let x;
+ if let Some(n) = Some("v") {
+ x = 1;
+ } else {
+ x = 2;
+ }
+
+ let x;
+ if true && let Some(n) = Some("let chains too") {
+ x = 1;
+ } else {
+ x = 2;
+ }
+
+ // ignore mut bindings
+ // https://github.com/shepmaster/twox-hash/blob/b169c16d86eb8ea4a296b0acb9d00ca7e3c3005f/src/sixty_four.rs#L88-L93
+ // https://github.com/dtolnay/thiserror/blob/21c26903e29cb92ba1a7ff11e82ae2001646b60d/tests/test_generics.rs#L91-L100
+ let mut x: usize;
+ x = 1;
+ x = 2;
+ x = 3;
+
+ // should not move the declaration if `x` has a significant drop, and there
+ // is another binding with a significant drop between it and the first usage
+ let x;
+ let y = SignificantDrop;
+ x = SignificantDrop;
+}
+
+#[rustfmt::skip]
+fn issue8911() -> u32 {
+ let x;
+ match 1 {
+ _ if { x = 1; false } => return 1,
+ _ => return 2,
+ }
+
+ let x;
+ if { x = 1; true } {
+ return 1;
+ } else {
+ return 2;
+ }
+
+ 3
+}
--- /dev/null
- --> $DIR/needless_late_init.rs:23:5
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:26:5
++ --> $DIR/needless_late_init.rs:24:5
+ |
+LL | let a;
+ | ^^^^^^ created here
+LL | a = "zero";
+ | ^^^^^^^^^^ initialised here
+ |
+ = note: `-D clippy::needless-late-init` implied by `-D warnings`
+help: declare `a` here
+ |
+LL | let a = "zero";
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:27:5
++ --> $DIR/needless_late_init.rs:27:5
+ |
+LL | let b;
+ | ^^^^^^ created here
+LL | let c;
+LL | b = 1;
+ | ^^^^^ initialised here
+ |
+help: declare `b` here
+ |
+LL | let b = 1;
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:31:5
++ --> $DIR/needless_late_init.rs:28:5
+ |
+LL | let c;
+ | ^^^^^^ created here
+LL | b = 1;
+LL | c = 2;
+ | ^^^^^ initialised here
+ |
+help: declare `c` here
+ |
+LL | let c = 2;
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:34:5
++ --> $DIR/needless_late_init.rs:32:5
+ |
+LL | let d: usize;
+ | ^^^^^^^^^^^^^ created here
+LL | d = 1;
+ | ^^^^^ initialised here
+ |
+help: declare `d` here
+ |
+LL | let d: usize = 1;
+ | ~~~~~~~~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:39:5
++ --> $DIR/needless_late_init.rs:35:5
+ |
+LL | let e;
+ | ^^^^^^ created here
+LL | e = format!("{}", d);
+ | ^^^^^^^^^^^^^^^^^^^^ initialised here
+ |
+help: declare `e` here
+ |
+LL | let e = format!("{}", d);
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:48:5
++ --> $DIR/needless_late_init.rs:40:5
+ |
+LL | let a;
+ | ^^^^^^
+ |
+help: declare `a` here
+ |
+LL | let a = match n {
+ | +++++++
+help: remove the assignments from the `match` arms
+ |
+LL ~ 1 => "one",
+LL | _ => {
+LL ~ "two"
+ |
+help: add a semicolon after the `match` expression
+ |
+LL | };
+ | +
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:55:5
++ --> $DIR/needless_late_init.rs:49:5
+ |
+LL | let b;
+ | ^^^^^^
+ |
+help: declare `b` here
+ |
+LL | let b = if n == 3 {
+ | +++++++
+help: remove the assignments from the branches
+ |
+LL ~ "four"
+LL | } else {
+LL ~ "five"
+ |
+help: add a semicolon after the `if` expression
+ |
+LL | };
+ | +
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:63:5
++ --> $DIR/needless_late_init.rs:56:5
+ |
+LL | let d;
+ | ^^^^^^
+ |
+help: declare `d` here
+ |
+LL | let d = if true {
+ | +++++++
+help: remove the assignments from the branches
+ |
+LL ~ temp
+LL | } else {
+LL ~ 15
+ |
+help: add a semicolon after the `if` expression
+ |
+LL | };
+ | +
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:70:5
++ --> $DIR/needless_late_init.rs:64:5
+ |
+LL | let e;
+ | ^^^^^^
+ |
+help: declare `e` here
+ |
+LL | let e = if true {
+ | +++++++
+help: remove the assignments from the branches
+ |
+LL ~ format!("{} {}", a, b)
+LL | } else {
+LL ~ format!("{}", n)
+ |
+help: add a semicolon after the `if` expression
+ |
+LL | };
+ | +
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:76:5
++ --> $DIR/needless_late_init.rs:71:5
+ |
+LL | let f;
+ | ^^^^^^
+ |
+help: declare `f` here
+ |
+LL | let f = match 1 {
+ | +++++++
+help: remove the assignments from the `match` arms
+ |
+LL - 1 => f = "three",
+LL + 1 => "three",
+ |
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:84:5
++ --> $DIR/needless_late_init.rs:77:5
+ |
+LL | let g: usize;
+ | ^^^^^^^^^^^^^
+ |
+help: declare `g` here
+ |
+LL | let g: usize = if true {
+ | ++++++++++++++
+help: remove the assignments from the branches
+ |
+LL - g = 5;
+LL + 5
+ |
+help: add a semicolon after the `if` expression
+ |
+LL | };
+ | +
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:88:5
++ --> $DIR/needless_late_init.rs:85:5
+ |
+LL | let x;
+ | ^^^^^^ created here
+LL | let y = SignificantDrop;
+LL | x = 1;
+ | ^^^^^ initialised here
+ |
+help: declare `x` here
+ |
+LL | let x = 1;
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:92:5
++ --> $DIR/needless_late_init.rs:89:5
+ |
+LL | let x;
+ | ^^^^^^ created here
+LL | let y = 1;
+LL | x = SignificantDrop;
+ | ^^^^^^^^^^^^^^^^^^^ initialised here
+ |
+help: declare `x` here
+ |
+LL | let x = SignificantDrop;
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:111:5
++ --> $DIR/needless_late_init.rs:93:5
+ |
+LL | let x;
+ | ^^^^^^ created here
+...
+LL | x = SignificantDrop;
+ | ^^^^^^^^^^^^^^^^^^^ initialised here
+ |
+help: declare `x` here
+ |
+LL | let x = SignificantDrop;
+ | ~~~~~
+
+error: unneeded late initialization
- --> $DIR/needless_late_init.rs:128:5
++ --> $DIR/needless_late_init.rs:112:5
+ |
+LL | let a;
+ | ^^^^^^
+ |
+help: declare `a` here
+ |
+LL | let a = match n {
+ | +++++++
+help: remove the assignments from the `match` arms
+ |
+LL ~ 1 => f().await,
+LL | _ => {
+LL ~ "two"
+ |
+help: add a semicolon after the `match` expression
+ |
+LL | };
+ | +
+
+error: unneeded late initialization
++ --> $DIR/needless_late_init.rs:129:5
+ |
+LL | let a;
+ | ^^^^^^
+ |
+help: declare `a` here
+ |
+LL | let a = match n {
+ | +++++++
+help: remove the assignments from the `match` arms
+ |
+LL ~ 1 => f(),
+LL | _ => {
+LL ~ "two"
+ |
+help: add a semicolon after the `match` expression
+ |
+LL | };
+ | +
+
+error: aborting due to 16 previous errors
+
--- /dev/null
- dead_code,
- clippy::single_match,
- clippy::redundant_pattern_matching,
+#![warn(clippy::needless_pass_by_value)]
++#![allow(dead_code)]
+#![allow(
- clippy::redundant_clone
+ clippy::option_option,
++ clippy::redundant_clone,
++ clippy::redundant_pattern_matching,
++ clippy::single_match,
++ clippy::uninlined_format_args
+)]
+
+use std::borrow::Borrow;
+use std::collections::HashSet;
+use std::convert::AsRef;
+use std::mem::MaybeUninit;
+
+// `v` should be warned
+// `w`, `x` and `y` are allowed (moved or mutated)
+fn foo<T: Default>(v: Vec<T>, w: Vec<T>, mut x: Vec<T>, y: Vec<T>) -> Vec<T> {
+ assert_eq!(v.len(), 42);
+
+ consume(w);
+
+ x.push(T::default());
+
+ y
+}
+
+fn consume<T>(_: T) {}
+
+struct Wrapper(String);
+
+fn bar(x: String, y: Wrapper) {
+ assert_eq!(x.len(), 42);
+ assert_eq!(y.0.len(), 42);
+}
+
+// V implements `Borrow<V>`, but should be warned correctly
+fn test_borrow_trait<T: Borrow<str>, U: AsRef<str>, V>(t: T, u: U, v: V) {
+ println!("{}", t.borrow());
+ println!("{}", u.as_ref());
+ consume(&v);
+}
+
+// ok
+fn test_fn<F: Fn(i32) -> i32>(f: F) {
+ f(1);
+}
+
+// x should be warned, but y is ok
+fn test_match(x: Option<Option<String>>, y: Option<Option<String>>) {
+ match x {
+ Some(Some(_)) => 1, // not moved
+ _ => 0,
+ };
+
+ match y {
+ Some(Some(s)) => consume(s), // moved
+ _ => (),
+ };
+}
+
+// x and y should be warned, but z is ok
+fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
+ let Wrapper(s) = z; // moved
+ let Wrapper(ref t) = y; // not moved
+ let Wrapper(_) = y; // still not moved
+
+ assert_eq!(x.0.len(), s.len());
+ println!("{}", t);
+}
+
+trait Foo {}
+
+// `S: Serialize` is allowed to be passed by value, since a caller can pass `&S` instead
+trait Serialize {}
+impl<'a, T> Serialize for &'a T where T: Serialize {}
+impl Serialize for i32 {}
+
+fn test_blanket_ref<T: Foo, S: Serialize>(_foo: T, _serializable: S) {}
+
+fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
+ s.capacity();
+ let _ = t.clone();
+ u.capacity();
+ let _ = v.clone();
+}
+
+struct S<T, U>(T, U);
+
+impl<T: Serialize, U> S<T, U> {
+ fn foo(
+ self,
+ // taking `self` by value is always allowed
+ s: String,
+ t: String,
+ ) -> usize {
+ s.len() + t.capacity()
+ }
+
+ fn bar(_t: T, // Ok, since `&T: Serialize` too
+ ) {
+ }
+
+ fn baz(&self, _u: U, _s: Self) {}
+}
+
+trait FalsePositive {
+ fn visit_str(s: &str);
+ fn visit_string(s: String) {
+ Self::visit_str(&s);
+ }
+}
+
+// shouldn't warn on extern funcs
+extern "C" fn ext(x: MaybeUninit<usize>) -> usize {
+ unsafe { x.assume_init() }
+}
+
+// exempt RangeArgument
+fn range<T: ::std::ops::RangeBounds<usize>>(range: T) {
+ let _ = range.start_bound();
+}
+
+struct CopyWrapper(u32);
+
+fn bar_copy(x: u32, y: CopyWrapper) {
+ assert_eq!(x, 42);
+ assert_eq!(y.0, 42);
+}
+
+// x and y should be warned, but z is ok
+fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
+ let CopyWrapper(s) = z; // moved
+ let CopyWrapper(ref t) = y; // not moved
+ let CopyWrapper(_) = y; // still not moved
+
+ assert_eq!(x.0, s);
+ println!("{}", t);
+}
+
+// The following 3 lines should not cause an ICE. See #2831
+trait Bar<'a, A> {}
+impl<'b, T> Bar<'b, T> for T {}
+fn some_fun<'b, S: Bar<'b, ()>>(_item: S) {}
+
+// Also this should not cause an ICE. See #2831
+trait Club<'a, A> {}
+impl<T> Club<'static, T> for T {}
+fn more_fun(_item: impl Club<'static, i32>) {}
+
+fn is_sync<T>(_: T)
+where
+ T: Sync,
+{
+}
+
+fn main() {
+ // This should not cause an ICE either
+ // https://github.com/rust-lang/rust-clippy/issues/3144
+ is_sync(HashSet::<usize>::new());
+}
--- /dev/null
- --> $DIR/needless_pass_by_value.rs:17:23
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:31:11
++ --> $DIR/needless_pass_by_value.rs:18:23
+ |
+LL | fn foo<T: Default>(v: Vec<T>, w: Vec<T>, mut x: Vec<T>, y: Vec<T>) -> Vec<T> {
+ | ^^^^^^ help: consider changing the type to: `&[T]`
+ |
+ = note: `-D clippy::needless-pass-by-value` implied by `-D warnings`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:31:22
++ --> $DIR/needless_pass_by_value.rs:32:11
+ |
+LL | fn bar(x: String, y: Wrapper) {
+ | ^^^^^^ help: consider changing the type to: `&str`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:37:71
++ --> $DIR/needless_pass_by_value.rs:32:22
+ |
+LL | fn bar(x: String, y: Wrapper) {
+ | ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:49:18
++ --> $DIR/needless_pass_by_value.rs:38:71
+ |
+LL | fn test_borrow_trait<T: Borrow<str>, U: AsRef<str>, V>(t: T, u: U, v: V) {
+ | ^ help: consider taking a reference instead: `&V`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:62:24
++ --> $DIR/needless_pass_by_value.rs:50:18
+ |
+LL | fn test_match(x: Option<Option<String>>, y: Option<Option<String>>) {
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&Option<Option<String>>`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:62:36
++ --> $DIR/needless_pass_by_value.rs:63:24
+ |
+LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
+ | ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:78:49
++ --> $DIR/needless_pass_by_value.rs:63:36
+ |
+LL | fn test_destructure(x: Wrapper, y: Wrapper, z: Wrapper) {
+ | ^^^^^^^ help: consider taking a reference instead: `&Wrapper`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:80:18
++ --> $DIR/needless_pass_by_value.rs:79:49
+ |
+LL | fn test_blanket_ref<T: Foo, S: Serialize>(_foo: T, _serializable: S) {}
+ | ^ help: consider taking a reference instead: `&T`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:80:29
++ --> $DIR/needless_pass_by_value.rs:81:18
+ |
+LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
+ | ^^^^^^ help: consider taking a reference instead: `&String`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:80:40
++ --> $DIR/needless_pass_by_value.rs:81:29
+ |
+LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
+ | ^^^^^^
+ |
+help: consider changing the type to
+ |
+LL | fn issue_2114(s: String, t: &str, u: Vec<i32>, v: Vec<i32>) {
+ | ~~~~
+help: change `t.clone()` to
+ |
+LL | let _ = t.to_string();
+ | ~~~~~~~~~~~~~
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:80:53
++ --> $DIR/needless_pass_by_value.rs:81:40
+ |
+LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
+ | ^^^^^^^^ help: consider taking a reference instead: `&Vec<i32>`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:93:12
++ --> $DIR/needless_pass_by_value.rs:81:53
+ |
+LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: Vec<i32>) {
+ | ^^^^^^^^
+ |
+help: consider changing the type to
+ |
+LL | fn issue_2114(s: String, t: String, u: Vec<i32>, v: &[i32]) {
+ | ~~~~~~
+help: change `v.clone()` to
+ |
+LL | let _ = v.to_owned();
+ | ~~~~~~~~~~~~
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:94:12
++ --> $DIR/needless_pass_by_value.rs:94:12
+ |
+LL | s: String,
+ | ^^^^^^ help: consider changing the type to: `&str`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:103:23
++ --> $DIR/needless_pass_by_value.rs:95:12
+ |
+LL | t: String,
+ | ^^^^^^ help: consider taking a reference instead: `&String`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:103:30
++ --> $DIR/needless_pass_by_value.rs:104:23
+ |
+LL | fn baz(&self, _u: U, _s: Self) {}
+ | ^ help: consider taking a reference instead: `&U`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:125:24
++ --> $DIR/needless_pass_by_value.rs:104:30
+ |
+LL | fn baz(&self, _u: U, _s: Self) {}
+ | ^^^^ help: consider taking a reference instead: `&Self`
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:123:1
++ --> $DIR/needless_pass_by_value.rs:126:24
+ |
+LL | fn bar_copy(x: u32, y: CopyWrapper) {
+ | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+ |
+help: consider marking this type as `Copy`
- --> $DIR/needless_pass_by_value.rs:131:29
++ --> $DIR/needless_pass_by_value.rs:124:1
+ |
+LL | struct CopyWrapper(u32);
+ | ^^^^^^^^^^^^^^^^^^
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:123:1
++ --> $DIR/needless_pass_by_value.rs:132:29
+ |
+LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
+ | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+ |
+help: consider marking this type as `Copy`
- --> $DIR/needless_pass_by_value.rs:131:45
++ --> $DIR/needless_pass_by_value.rs:124:1
+ |
+LL | struct CopyWrapper(u32);
+ | ^^^^^^^^^^^^^^^^^^
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:123:1
++ --> $DIR/needless_pass_by_value.rs:132:45
+ |
+LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
+ | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+ |
+help: consider marking this type as `Copy`
- --> $DIR/needless_pass_by_value.rs:131:61
++ --> $DIR/needless_pass_by_value.rs:124:1
+ |
+LL | struct CopyWrapper(u32);
+ | ^^^^^^^^^^^^^^^^^^
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:123:1
++ --> $DIR/needless_pass_by_value.rs:132:61
+ |
+LL | fn test_destructure_copy(x: CopyWrapper, y: CopyWrapper, z: CopyWrapper) {
+ | ^^^^^^^^^^^ help: consider taking a reference instead: `&CopyWrapper`
+ |
+help: consider marking this type as `Copy`
- --> $DIR/needless_pass_by_value.rs:143:40
++ --> $DIR/needless_pass_by_value.rs:124:1
+ |
+LL | struct CopyWrapper(u32);
+ | ^^^^^^^^^^^^^^^^^^
+
+error: this argument is passed by value, but not consumed in the function body
- --> $DIR/needless_pass_by_value.rs:148:20
++ --> $DIR/needless_pass_by_value.rs:144:40
+ |
+LL | fn some_fun<'b, S: Bar<'b, ()>>(_item: S) {}
+ | ^ help: consider taking a reference instead: `&S`
+
+error: this argument is passed by value, but not consumed in the function body
++ --> $DIR/needless_pass_by_value.rs:149:20
+ |
+LL | fn more_fun(_item: impl Club<'static, i32>) {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&impl Club<'static, i32>`
+
+error: aborting due to 22 previous errors
+
--- /dev/null
+#![warn(clippy::needless_range_loop)]
++#![allow(clippy::uninlined_format_args)]
+
+static STATIC: [usize; 4] = [0, 1, 8, 16];
+const CONST: [usize; 4] = [0, 1, 8, 16];
+const MAX_LEN: usize = 42;
+
+fn main() {
+ let mut vec = vec![1, 2, 3, 4];
+ let vec2 = vec![1, 2, 3, 4];
+ for i in 0..vec.len() {
+ println!("{}", vec[i]);
+ }
+
+ for i in 0..vec.len() {
+ let i = 42; // make a different `i`
+ println!("{}", vec[i]); // ok, not the `i` of the for-loop
+ }
+
+ for i in 0..vec.len() {
+ let _ = vec[i];
+ }
+
+ // ICE #746
+ for j in 0..4 {
+ println!("{:?}", STATIC[j]);
+ }
+
+ for j in 0..4 {
+ println!("{:?}", CONST[j]);
+ }
+
+ for i in 0..vec.len() {
+ println!("{} {}", vec[i], i);
+ }
+ for i in 0..vec.len() {
+ // not an error, indexing more than one variable
+ println!("{} {}", vec[i], vec2[i]);
+ }
+
+ for i in 0..vec.len() {
+ println!("{}", vec2[i]);
+ }
+
+ for i in 5..vec.len() {
+ println!("{}", vec[i]);
+ }
+
+ for i in 0..MAX_LEN {
+ println!("{}", vec[i]);
+ }
+
+ for i in 0..=MAX_LEN {
+ println!("{}", vec[i]);
+ }
+
+ for i in 5..10 {
+ println!("{}", vec[i]);
+ }
+
+ for i in 5..=10 {
+ println!("{}", vec[i]);
+ }
+
+ for i in 5..vec.len() {
+ println!("{} {}", vec[i], i);
+ }
+
+ for i in 5..10 {
+ println!("{} {}", vec[i], i);
+ }
+
+ // #2542
+ for i in 0..vec.len() {
+ vec[i] = Some(1).unwrap_or_else(|| panic!("error on {}", i));
+ }
+
+ // #3788
+ let test = Test {
+ inner: vec![1, 2, 3, 4],
+ };
+ for i in 0..2 {
+ println!("{}", test[i]);
+ }
+}
+
+struct Test {
+ inner: Vec<usize>,
+}
+
+impl std::ops::Index<usize> for Test {
+ type Output = usize;
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.inner[index]
+ }
+}
--- /dev/null
- --> $DIR/needless_range_loop.rs:10:14
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:19:14
++ --> $DIR/needless_range_loop.rs:11:14
+ |
+LL | for i in 0..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+ = note: `-D clippy::needless-range-loop` implied by `-D warnings`
+help: consider using an iterator
+ |
+LL | for <item> in &vec {
+ | ~~~~~~ ~~~~
+
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:24:14
++ --> $DIR/needless_range_loop.rs:20:14
+ |
+LL | for i in 0..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in &vec {
+ | ~~~~~~ ~~~~
+
+error: the loop variable `j` is only used to index `STATIC`
- --> $DIR/needless_range_loop.rs:28:14
++ --> $DIR/needless_range_loop.rs:25:14
+ |
+LL | for j in 0..4 {
+ | ^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in &STATIC {
+ | ~~~~~~ ~~~~~~~
+
+error: the loop variable `j` is only used to index `CONST`
- --> $DIR/needless_range_loop.rs:32:14
++ --> $DIR/needless_range_loop.rs:29:14
+ |
+LL | for j in 0..4 {
+ | ^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in &CONST {
+ | ~~~~~~ ~~~~~~
+
+error: the loop variable `i` is used to index `vec`
- --> $DIR/needless_range_loop.rs:40:14
++ --> $DIR/needless_range_loop.rs:33:14
+ |
+LL | for i in 0..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for (i, <item>) in vec.iter().enumerate() {
+ | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is only used to index `vec2`
- --> $DIR/needless_range_loop.rs:44:14
++ --> $DIR/needless_range_loop.rs:41:14
+ |
+LL | for i in 0..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in vec2.iter().take(vec.len()) {
+ | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:48:14
++ --> $DIR/needless_range_loop.rs:45:14
+ |
+LL | for i in 5..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in vec.iter().skip(5) {
+ | ~~~~~~ ~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:52:14
++ --> $DIR/needless_range_loop.rs:49:14
+ |
+LL | for i in 0..MAX_LEN {
+ | ^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in vec.iter().take(MAX_LEN) {
+ | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:56:14
++ --> $DIR/needless_range_loop.rs:53:14
+ |
+LL | for i in 0..=MAX_LEN {
+ | ^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in vec.iter().take(MAX_LEN + 1) {
+ | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:60:14
++ --> $DIR/needless_range_loop.rs:57:14
+ |
+LL | for i in 5..10 {
+ | ^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in vec.iter().take(10).skip(5) {
+ | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is only used to index `vec`
- --> $DIR/needless_range_loop.rs:64:14
++ --> $DIR/needless_range_loop.rs:61:14
+ |
+LL | for i in 5..=10 {
+ | ^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for <item> in vec.iter().take(10 + 1).skip(5) {
+ | ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is used to index `vec`
- --> $DIR/needless_range_loop.rs:68:14
++ --> $DIR/needless_range_loop.rs:65:14
+ |
+LL | for i in 5..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for (i, <item>) in vec.iter().enumerate().skip(5) {
+ | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is used to index `vec`
- --> $DIR/needless_range_loop.rs:73:14
++ --> $DIR/needless_range_loop.rs:69:14
+ |
+LL | for i in 5..10 {
+ | ^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for (i, <item>) in vec.iter().enumerate().take(10).skip(5) {
+ | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: the loop variable `i` is used to index `vec`
++ --> $DIR/needless_range_loop.rs:74:14
+ |
+LL | for i in 0..vec.len() {
+ | ^^^^^^^^^^^^
+ |
+help: consider using an iterator
+ |
+LL | for (i, <item>) in vec.iter_mut().enumerate() {
+ | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 14 previous errors
+
--- /dev/null
+// run-rustfix
+
+#![feature(lint_reasons)]
+#![allow(unused)]
+#![allow(
+ clippy::if_same_then_else,
+ clippy::single_match,
+ clippy::needless_bool,
+ clippy::equatable_if_let
+)]
+#![warn(clippy::needless_return)]
+
+use std::cell::RefCell;
+
+macro_rules! the_answer {
+ () => {
+ 42
+ };
+}
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ true
+}
+
+fn test_no_semicolon() -> bool {
+ true
+}
+
+fn test_if_block() -> bool {
+ if true {
+ true
+ } else {
+ false
+ }
+}
+
+fn test_match(x: bool) -> bool {
+ match x {
+ true => false,
+ false => {
+ true
+ },
+ }
+}
+
+fn test_closure() {
+ let _ = || {
+ true
+ };
+ let _ = || true;
+}
+
+fn test_macro_call() -> i32 {
+ the_answer!()
+}
+
+fn test_void_fun() {
+
+}
+
+fn test_void_if_fun(b: bool) {
+ if b {
+
+ } else {
+
+ }
+}
+
+fn test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => (),
+ }
+}
+
+fn test_nested_match(x: u32) {
+ match x {
+ 0 => (),
+ 1 => {
+ let _ = 42;
+
+ },
+ _ => (),
+ }
+}
+
+fn temporary_outlives_local() -> String {
+ let x = RefCell::<String>::default();
+ return x.borrow().clone();
+}
+
+fn borrows_but_not_last(value: bool) -> String {
+ if value {
+ let x = RefCell::<String>::default();
+ let _a = x.borrow().clone();
+ String::from("test")
+ } else {
+ String::new()
+ }
+}
+
+macro_rules! needed_return {
+ ($e:expr) => {
+ if $e > 3 {
+ return;
+ }
+ };
+}
+
+fn test_return_in_macro() {
+ // This will return and the macro below won't be executed. Removing the `return` from the macro
+ // will change semantics.
+ needed_return!(10);
+ needed_return!(0);
+}
+
+mod issue6501 {
+ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn foo(bar: Result<(), ()>) {
+ bar.unwrap_or_else(|_| {})
+ }
+
+ fn test_closure() {
+ let _ = || {
+
+ };
+ let _ = || {};
+ }
+
+ struct Foo;
+ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn bar(res: Result<Foo, u8>) -> Foo {
+ res.unwrap_or_else(|_| Foo)
+ }
+}
+
+async fn async_test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ true
+}
+
+async fn async_test_no_semicolon() -> bool {
+ true
+}
+
+async fn async_test_if_block() -> bool {
+ if true {
+ true
+ } else {
+ false
+ }
+}
+
+async fn async_test_match(x: bool) -> bool {
+ match x {
+ true => false,
+ false => {
+ true
+ },
+ }
+}
+
+async fn async_test_closure() {
+ let _ = || {
+ true
+ };
+ let _ = || true;
+}
+
+async fn async_test_macro_call() -> i32 {
+ the_answer!()
+}
+
+async fn async_test_void_fun() {
+
+}
+
+async fn async_test_void_if_fun(b: bool) {
+ if b {
+
+ } else {
+
+ }
+}
+
+async fn async_test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => (),
+ }
+}
+
+async fn async_temporary_outlives_local() -> String {
+ let x = RefCell::<String>::default();
+ return x.borrow().clone();
+}
+
+async fn async_borrows_but_not_last(value: bool) -> String {
+ if value {
+ let x = RefCell::<String>::default();
+ let _a = x.borrow().clone();
+ String::from("test")
+ } else {
+ String::new()
+ }
+}
+
+async fn async_test_return_in_macro() {
+ needed_return!(10);
+ needed_return!(0);
+}
+
+fn let_else() {
+ let Some(1) = Some(1) else { return };
+}
+
+fn needless_return_macro() -> String {
+ let _ = "foo";
+ let _ = "bar";
+ format!("Hello {}", "world!")
+}
+
+fn issue_9361() -> i32 {
+ #[allow(clippy::integer_arithmetic)]
+ return 1 + 2;
+}
+
++fn issue8336(x: i32) -> bool {
++ if x > 0 {
++ println!("something");
++ true
++ } else {
++ false
++ }
++}
++
++fn issue8156(x: u8) -> u64 {
++ match x {
++ 80 => {
++ 10
++ },
++ _ => {
++ 100
++ },
++ }
++}
++
++// Ideally the compiler should throw `unused_braces` in this case
++fn issue9192() -> i32 {
++ {
++ 0
++ }
++}
++
++fn issue9503(x: usize) -> isize {
++ unsafe {
++ if x > 12 {
++ *(x as *const isize)
++ } else {
++ !*(x as *const isize)
++ }
++ }
++}
++
+fn main() {}
--- /dev/null
+// run-rustfix
+
+#![feature(lint_reasons)]
+#![allow(unused)]
+#![allow(
+ clippy::if_same_then_else,
+ clippy::single_match,
+ clippy::needless_bool,
+ clippy::equatable_if_let
+)]
+#![warn(clippy::needless_return)]
+
+use std::cell::RefCell;
+
+macro_rules! the_answer {
+ () => {
+ 42
+ };
+}
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ return true;
+}
+
+fn test_no_semicolon() -> bool {
+ return true;
+}
+
+fn test_if_block() -> bool {
+ if true {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+fn test_match(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => {
+ return true;
+ },
+ }
+}
+
+fn test_closure() {
+ let _ = || {
+ return true;
+ };
+ let _ = || return true;
+}
+
+fn test_macro_call() -> i32 {
+ return the_answer!();
+}
+
+fn test_void_fun() {
+ return;
+}
+
+fn test_void_if_fun(b: bool) {
+ if b {
+ return;
+ } else {
+ return;
+ }
+}
+
+fn test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => return,
+ }
+}
+
+fn test_nested_match(x: u32) {
+ match x {
+ 0 => (),
+ 1 => {
+ let _ = 42;
+ return;
+ },
+ _ => return,
+ }
+}
+
+fn temporary_outlives_local() -> String {
+ let x = RefCell::<String>::default();
+ return x.borrow().clone();
+}
+
+fn borrows_but_not_last(value: bool) -> String {
+ if value {
+ let x = RefCell::<String>::default();
+ let _a = x.borrow().clone();
+ return String::from("test");
+ } else {
+ return String::new();
+ }
+}
+
+macro_rules! needed_return {
+ ($e:expr) => {
+ if $e > 3 {
+ return;
+ }
+ };
+}
+
+fn test_return_in_macro() {
+ // This will return and the macro below won't be executed. Removing the `return` from the macro
+ // will change semantics.
+ needed_return!(10);
+ needed_return!(0);
+}
+
+mod issue6501 {
+ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn foo(bar: Result<(), ()>) {
+ bar.unwrap_or_else(|_| return)
+ }
+
+ fn test_closure() {
+ let _ = || {
+ return;
+ };
+ let _ = || return;
+ }
+
+ struct Foo;
+ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn bar(res: Result<Foo, u8>) -> Foo {
+ res.unwrap_or_else(|_| return Foo)
+ }
+}
+
+async fn async_test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ return true;
+}
+
+async fn async_test_no_semicolon() -> bool {
+ return true;
+}
+
+async fn async_test_if_block() -> bool {
+ if true {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+async fn async_test_match(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => {
+ return true;
+ },
+ }
+}
+
+async fn async_test_closure() {
+ let _ = || {
+ return true;
+ };
+ let _ = || return true;
+}
+
+async fn async_test_macro_call() -> i32 {
+ return the_answer!();
+}
+
+async fn async_test_void_fun() {
+ return;
+}
+
+async fn async_test_void_if_fun(b: bool) {
+ if b {
+ return;
+ } else {
+ return;
+ }
+}
+
+async fn async_test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => return,
+ }
+}
+
+async fn async_temporary_outlives_local() -> String {
+ let x = RefCell::<String>::default();
+ return x.borrow().clone();
+}
+
+async fn async_borrows_but_not_last(value: bool) -> String {
+ if value {
+ let x = RefCell::<String>::default();
+ let _a = x.borrow().clone();
+ return String::from("test");
+ } else {
+ return String::new();
+ }
+}
+
+async fn async_test_return_in_macro() {
+ needed_return!(10);
+ needed_return!(0);
+}
+
+fn let_else() {
+ let Some(1) = Some(1) else { return };
+}
+
+fn needless_return_macro() -> String {
+ let _ = "foo";
+ let _ = "bar";
+ return format!("Hello {}", "world!");
+}
+
+fn issue_9361() -> i32 {
+ #[allow(clippy::integer_arithmetic)]
+ return 1 + 2;
+}
+
++fn issue8336(x: i32) -> bool {
++ if x > 0 {
++ println!("something");
++ return true;
++ } else {
++ return false;
++ };
++}
++
++fn issue8156(x: u8) -> u64 {
++ match x {
++ 80 => {
++ return 10;
++ },
++ _ => {
++ return 100;
++ },
++ };
++}
++
++// Ideally the compiler should throw `unused_braces` in this case
++fn issue9192() -> i32 {
++ {
++ return 0;
++ };
++}
++
++fn issue9503(x: usize) -> isize {
++ unsafe {
++ if x > 12 {
++ return *(x as *const isize);
++ } else {
++ return !*(x as *const isize);
++ };
++ };
++}
++
+fn main() {}
--- /dev/null
- | ^^^^^^^^^^^^ help: remove `return`: `true`
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:26:5
+ |
+LL | return true;
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
+ |
+ = note: `-D clippy::needless-return` implied by `-D warnings`
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:30:5
+ |
+LL | return true;
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:35:9
+ |
+LL | return true;
- | ^^^^^^^^^^^^^ help: remove `return`: `false`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:37:9
+ |
+LL | return false;
- | ^^^^^^^^^^^^ help: remove `return`: `false`
++ | ^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:43:17
+ |
+LL | true => return false,
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:45:13
+ |
+LL | return true;
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:52:9
+ |
+LL | return true;
- | ^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:54:16
+ |
+LL | let _ = || return true;
- | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `the_answer!()`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:58:5
+ |
+LL | return the_answer!();
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:62:5
+ |
+LL | return;
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:67:9
+ |
+LL | return;
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:69:9
+ |
+LL | return;
- | ^^^^^^ help: replace `return` with a unit value: `()`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:76:14
+ |
+LL | _ => return,
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^
++ |
++ = help: replace `return` with a unit value
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:85:13
+ |
+LL | return;
- | ^^^^^^ help: replace `return` with a unit value: `()`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:87:14
+ |
+LL | _ => return,
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")`
++ | ^^^^^^
++ |
++ = help: replace `return` with a unit value
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:100:9
+ |
+LL | return String::from("test");
- | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()`
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:102:9
+ |
+LL | return String::new();
- | ^^^^^^ help: replace `return` with an empty block: `{}`
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:124:32
+ |
+LL | bar.unwrap_or_else(|_| return)
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^
++ |
++ = help: replace `return` with an empty block
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:129:13
+ |
+LL | return;
- | ^^^^^^ help: replace `return` with an empty block: `{}`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:131:20
+ |
+LL | let _ = || return;
- | ^^^^^^^^^^ help: remove `return`: `Foo`
++ | ^^^^^^
++ |
++ = help: replace `return` with an empty block
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:137:32
+ |
+LL | res.unwrap_or_else(|_| return Foo)
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:146:5
+ |
+LL | return true;
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:150:5
+ |
+LL | return true;
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:155:9
+ |
+LL | return true;
- | ^^^^^^^^^^^^^ help: remove `return`: `false`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:157:9
+ |
+LL | return false;
- | ^^^^^^^^^^^^ help: remove `return`: `false`
++ | ^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:163:17
+ |
+LL | true => return false,
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:165:13
+ |
+LL | return true;
- | ^^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:172:9
+ |
+LL | return true;
- | ^^^^^^^^^^^ help: remove `return`: `true`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:174:16
+ |
+LL | let _ = || return true;
- | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `the_answer!()`
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:178:5
+ |
+LL | return the_answer!();
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:182:5
+ |
+LL | return;
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:187:9
+ |
+LL | return;
- | ^^^^^^^ help: remove `return`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:189:9
+ |
+LL | return;
- | ^^^^^^ help: replace `return` with a unit value: `()`
++ | ^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:196:14
+ |
+LL | _ => return,
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")`
++ | ^^^^^^
++ |
++ = help: replace `return` with a unit value
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:209:9
+ |
+LL | return String::from("test");
- | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()`
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:211:9
+ |
+LL | return String::new();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `format!("Hello {}", "world!")`
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:227:5
+ |
+LL | return format!("Hello {}", "world!");
- error: aborting due to 37 previous errors
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:238:9
++ |
++LL | return true;
++ | ^^^^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:240:9
++ |
++LL | return false;
++ | ^^^^^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:247:13
++ |
++LL | return 10;
++ | ^^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:250:13
++ |
++LL | return 100;
++ | ^^^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:258:9
++ |
++LL | return 0;
++ | ^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:265:13
++ |
++LL | return *(x as *const isize);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
++
++error: unneeded `return` statement
++ --> $DIR/needless_return.rs:267:13
++ |
++LL | return !*(x as *const isize);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: remove `return`
+
++error: aborting due to 44 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;
++ };
++}
++
+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 9 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: aborting due to 10 previous errors
+
--- /dev/null
- #![allow(dead_code)]
- #![allow(path_statements)]
- #![allow(clippy::deref_addrof)]
- #![allow(clippy::redundant_field_names)]
+#![feature(box_syntax, fn_traits, unboxed_closures)]
+#![warn(clippy::no_effect_underscore_binding)]
++#![allow(dead_code, path_statements)]
++#![allow(clippy::deref_addrof, clippy::redundant_field_names, clippy::uninlined_format_args)]
+
+struct Unit;
+struct Tuple(i32);
+struct Struct {
+ field: i32,
+}
+enum Enum {
+ Tuple(i32),
+ Struct { field: i32 },
+}
+struct DropUnit;
+impl Drop for DropUnit {
+ fn drop(&mut self) {}
+}
+struct DropStruct {
+ field: i32,
+}
+impl Drop for DropStruct {
+ fn drop(&mut self) {}
+}
+struct DropTuple(i32);
+impl Drop for DropTuple {
+ fn drop(&mut self) {}
+}
+enum DropEnum {
+ Tuple(i32),
+ Struct { field: i32 },
+}
+impl Drop for DropEnum {
+ fn drop(&mut self) {}
+}
+struct FooString {
+ s: String,
+}
+union Union {
+ a: u8,
+ b: f64,
+}
+
+fn get_number() -> i32 {
+ 0
+}
+fn get_struct() -> Struct {
+ Struct { field: 0 }
+}
+fn get_drop_struct() -> DropStruct {
+ DropStruct { field: 0 }
+}
+
+unsafe fn unsafe_fn() -> i32 {
+ 0
+}
+
+struct GreetStruct1;
+
+impl FnOnce<(&str,)> for GreetStruct1 {
+ type Output = ();
+
+ extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output {
+ println!("hello {}", who);
+ }
+}
+
+struct GreetStruct2();
+
+impl FnOnce<(&str,)> for GreetStruct2 {
+ type Output = ();
+
+ extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output {
+ println!("hello {}", who);
+ }
+}
+
+struct GreetStruct3;
+
+impl FnOnce<(&str,)> for GreetStruct3 {
+ type Output = ();
+
+ extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output {
+ println!("hello {}", who);
+ }
+}
+
+fn main() {
+ let s = get_struct();
+ let s2 = get_struct();
+
+ 0;
+ s2;
+ Unit;
+ Tuple(0);
+ Struct { field: 0 };
+ Struct { ..s };
+ Union { a: 0 };
+ Enum::Tuple(0);
+ Enum::Struct { field: 0 };
+ 5 + 6;
+ *&42;
+ &6;
+ (5, 6, 7);
+ box 42;
+ ..;
+ 5..;
+ ..5;
+ 5..6;
+ 5..=6;
+ [42, 55];
+ [42, 55][1];
+ (42, 55).1;
+ [42; 55];
+ [42; 55][13];
+ let mut x = 0;
+ || x += 5;
+ let s: String = "foo".into();
+ FooString { s: s };
+ let _unused = 1;
+ let _penguin = || println!("Some helpful closure");
+ let _duck = Struct { field: 0 };
+ let _cat = [2, 4, 6, 8][2];
+
+ #[allow(clippy::no_effect)]
+ 0;
+
+ // Do not warn
+ get_number();
+ unsafe { unsafe_fn() };
+ let _used = get_struct();
+ let _x = vec![1];
+ DropUnit;
+ DropStruct { field: 0 };
+ DropTuple(0);
+ DropEnum::Tuple(0);
+ DropEnum::Struct { field: 0 };
+ GreetStruct1("world");
+ GreetStruct2()("world");
+ GreetStruct3 {}("world");
+}
--- /dev/null
- --> $DIR/no_effect.rs:94:5
+error: statement with no effect
- --> $DIR/no_effect.rs:95:5
++ --> $DIR/no_effect.rs:92:5
+ |
+LL | 0;
+ | ^^
+ |
+ = note: `-D clippy::no-effect` implied by `-D warnings`
+
+error: statement with no effect
- --> $DIR/no_effect.rs:96:5
++ --> $DIR/no_effect.rs:93:5
+ |
+LL | s2;
+ | ^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:97:5
++ --> $DIR/no_effect.rs:94:5
+ |
+LL | Unit;
+ | ^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:98:5
++ --> $DIR/no_effect.rs:95:5
+ |
+LL | Tuple(0);
+ | ^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:99:5
++ --> $DIR/no_effect.rs:96:5
+ |
+LL | Struct { field: 0 };
+ | ^^^^^^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:100:5
++ --> $DIR/no_effect.rs:97:5
+ |
+LL | Struct { ..s };
+ | ^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:101:5
++ --> $DIR/no_effect.rs:98:5
+ |
+LL | Union { a: 0 };
+ | ^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:102:5
++ --> $DIR/no_effect.rs:99:5
+ |
+LL | Enum::Tuple(0);
+ | ^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:103:5
++ --> $DIR/no_effect.rs:100:5
+ |
+LL | Enum::Struct { field: 0 };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:104:5
++ --> $DIR/no_effect.rs:101:5
+ |
+LL | 5 + 6;
+ | ^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:105:5
++ --> $DIR/no_effect.rs:102:5
+ |
+LL | *&42;
+ | ^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:106:5
++ --> $DIR/no_effect.rs:103:5
+ |
+LL | &6;
+ | ^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:107:5
++ --> $DIR/no_effect.rs:104:5
+ |
+LL | (5, 6, 7);
+ | ^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:108:5
++ --> $DIR/no_effect.rs:105:5
+ |
+LL | box 42;
+ | ^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:109:5
++ --> $DIR/no_effect.rs:106:5
+ |
+LL | ..;
+ | ^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:110:5
++ --> $DIR/no_effect.rs:107:5
+ |
+LL | 5..;
+ | ^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:111:5
++ --> $DIR/no_effect.rs:108:5
+ |
+LL | ..5;
+ | ^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:112:5
++ --> $DIR/no_effect.rs:109:5
+ |
+LL | 5..6;
+ | ^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:113:5
++ --> $DIR/no_effect.rs:110:5
+ |
+LL | 5..=6;
+ | ^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:114:5
++ --> $DIR/no_effect.rs:111:5
+ |
+LL | [42, 55];
+ | ^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:115:5
++ --> $DIR/no_effect.rs:112:5
+ |
+LL | [42, 55][1];
+ | ^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:116:5
++ --> $DIR/no_effect.rs:113:5
+ |
+LL | (42, 55).1;
+ | ^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:117:5
++ --> $DIR/no_effect.rs:114:5
+ |
+LL | [42; 55];
+ | ^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:119:5
++ --> $DIR/no_effect.rs:115:5
+ |
+LL | [42; 55][13];
+ | ^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:121:5
++ --> $DIR/no_effect.rs:117:5
+ |
+LL | || x += 5;
+ | ^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:122:5
++ --> $DIR/no_effect.rs:119:5
+ |
+LL | FooString { s: s };
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: binding to `_` prefixed variable with no side-effect
- --> $DIR/no_effect.rs:123:5
++ --> $DIR/no_effect.rs:120:5
+ |
+LL | let _unused = 1;
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::no-effect-underscore-binding` implied by `-D warnings`
+
+error: binding to `_` prefixed variable with no side-effect
- --> $DIR/no_effect.rs:124:5
++ --> $DIR/no_effect.rs:121:5
+ |
+LL | let _penguin = || println!("Some helpful closure");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: binding to `_` prefixed variable with no side-effect
- --> $DIR/no_effect.rs:125:5
++ --> $DIR/no_effect.rs:122:5
+ |
+LL | let _duck = Struct { field: 0 };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: binding to `_` prefixed variable with no side-effect
++ --> $DIR/no_effect.rs:123:5
+ |
+LL | let _cat = [2, 4, 6, 8][2];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 30 previous errors
+
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::unnecessary_wraps)]
+#![warn(clippy::option_map_unit_fn)]
+#![allow(unused)]
++#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+fn do_nothing<T>(_: T) {}
+
+fn diverge<T>(_: T) -> ! {
+ panic!()
+}
+
+fn plus_one(value: usize) -> usize {
+ value + 1
+}
+
+fn option() -> Option<usize> {
+ Some(10)
+}
+
+struct HasOption {
+ field: Option<usize>,
+}
+
+impl HasOption {
+ fn do_option_nothing(&self, value: usize) {}
+
+ fn do_option_plus_one(&self, value: usize) -> usize {
+ value + 1
+ }
+}
+#[rustfmt::skip]
+fn option_map_unit_fn() {
+ let x = HasOption { field: Some(10) };
+
+ x.field.map(plus_one);
+ let _ : Option<()> = x.field.map(do_nothing);
+
+ if let Some(x_field) = x.field { do_nothing(x_field) }
+
+ if let Some(x_field) = x.field { do_nothing(x_field) }
+
+ if let Some(x_field) = x.field { diverge(x_field) }
+
+ let captured = 10;
+ if let Some(value) = x.field { do_nothing(value + captured) };
+ let _ : Option<()> = x.field.map(|value| do_nothing(value + captured));
+
+ if let Some(value) = x.field { x.do_option_nothing(value + captured) }
+
+ if let Some(value) = x.field { x.do_option_plus_one(value + captured); }
+
+
+ if let Some(value) = x.field { do_nothing(value + captured) }
+
+ if let Some(value) = x.field { do_nothing(value + captured) }
+
+ if let Some(value) = x.field { do_nothing(value + captured); }
+
+ if let Some(value) = x.field { do_nothing(value + captured); }
+
+
+ if let Some(value) = x.field { diverge(value + captured) }
+
+ if let Some(value) = x.field { diverge(value + captured) }
+
+ if let Some(value) = x.field { diverge(value + captured); }
+
+ if let Some(value) = x.field { diverge(value + captured); }
+
+
+ x.field.map(|value| plus_one(value + captured));
+ x.field.map(|value| { plus_one(value + captured) });
+ if let Some(value) = x.field { let y = plus_one(value + captured); }
+
+ if let Some(value) = x.field { plus_one(value + captured); }
+
+ if let Some(value) = x.field { plus_one(value + captured); }
+
+
+ if let Some(ref value) = x.field { do_nothing(value + captured) }
+
+ if let Some(a) = option() { do_nothing(a) }
+
+ if let Some(value) = option() { println!("{:?}", value) }
+}
+
+fn main() {}
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::unnecessary_wraps)]
+#![warn(clippy::option_map_unit_fn)]
+#![allow(unused)]
++#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+fn do_nothing<T>(_: T) {}
+
+fn diverge<T>(_: T) -> ! {
+ panic!()
+}
+
+fn plus_one(value: usize) -> usize {
+ value + 1
+}
+
+fn option() -> Option<usize> {
+ Some(10)
+}
+
+struct HasOption {
+ field: Option<usize>,
+}
+
+impl HasOption {
+ fn do_option_nothing(&self, value: usize) {}
+
+ fn do_option_plus_one(&self, value: usize) -> usize {
+ value + 1
+ }
+}
+#[rustfmt::skip]
+fn option_map_unit_fn() {
+ let x = HasOption { field: Some(10) };
+
+ x.field.map(plus_one);
+ let _ : Option<()> = x.field.map(do_nothing);
+
+ x.field.map(do_nothing);
+
+ x.field.map(do_nothing);
+
+ x.field.map(diverge);
+
+ let captured = 10;
+ if let Some(value) = x.field { do_nothing(value + captured) };
+ let _ : Option<()> = x.field.map(|value| do_nothing(value + captured));
+
+ x.field.map(|value| x.do_option_nothing(value + captured));
+
+ x.field.map(|value| { x.do_option_plus_one(value + captured); });
+
+
+ x.field.map(|value| do_nothing(value + captured));
+
+ x.field.map(|value| { do_nothing(value + captured) });
+
+ x.field.map(|value| { do_nothing(value + captured); });
+
+ x.field.map(|value| { { do_nothing(value + captured); } });
+
+
+ x.field.map(|value| diverge(value + captured));
+
+ x.field.map(|value| { diverge(value + captured) });
+
+ x.field.map(|value| { diverge(value + captured); });
+
+ x.field.map(|value| { { diverge(value + captured); } });
+
+
+ x.field.map(|value| plus_one(value + captured));
+ x.field.map(|value| { plus_one(value + captured) });
+ x.field.map(|value| { let y = plus_one(value + captured); });
+
+ x.field.map(|value| { plus_one(value + captured); });
+
+ x.field.map(|value| { { plus_one(value + captured); } });
+
+
+ x.field.map(|ref value| { do_nothing(value + captured) });
+
+ option().map(do_nothing);
+
+ option().map(|value| println!("{:?}", value));
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/option_map_unit_fn_fixable.rs:39:5
+error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:41:5
++ --> $DIR/option_map_unit_fn_fixable.rs:38:5
+ |
+LL | x.field.map(do_nothing);
+ | ^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(x_field) = x.field { do_nothing(x_field) }`
+ |
+ = note: `-D clippy::option-map-unit-fn` implied by `-D warnings`
+
+error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:43:5
++ --> $DIR/option_map_unit_fn_fixable.rs:40:5
+ |
+LL | x.field.map(do_nothing);
+ | ^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(x_field) = x.field { do_nothing(x_field) }`
+
+error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:49:5
++ --> $DIR/option_map_unit_fn_fixable.rs:42:5
+ |
+LL | x.field.map(diverge);
+ | ^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(x_field) = x.field { diverge(x_field) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:51:5
++ --> $DIR/option_map_unit_fn_fixable.rs:48:5
+ |
+LL | x.field.map(|value| x.do_option_nothing(value + captured));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { x.do_option_nothing(value + captured) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:54:5
++ --> $DIR/option_map_unit_fn_fixable.rs:50:5
+ |
+LL | x.field.map(|value| { x.do_option_plus_one(value + captured); });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { x.do_option_plus_one(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:56:5
++ --> $DIR/option_map_unit_fn_fixable.rs:53:5
+ |
+LL | x.field.map(|value| do_nothing(value + captured));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { do_nothing(value + captured) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:58:5
++ --> $DIR/option_map_unit_fn_fixable.rs:55:5
+ |
+LL | x.field.map(|value| { do_nothing(value + captured) });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { do_nothing(value + captured) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:60:5
++ --> $DIR/option_map_unit_fn_fixable.rs:57:5
+ |
+LL | x.field.map(|value| { do_nothing(value + captured); });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { do_nothing(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:63:5
++ --> $DIR/option_map_unit_fn_fixable.rs:59:5
+ |
+LL | x.field.map(|value| { { do_nothing(value + captured); } });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { do_nothing(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:65:5
++ --> $DIR/option_map_unit_fn_fixable.rs:62:5
+ |
+LL | x.field.map(|value| diverge(value + captured));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { diverge(value + captured) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:67:5
++ --> $DIR/option_map_unit_fn_fixable.rs:64:5
+ |
+LL | x.field.map(|value| { diverge(value + captured) });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { diverge(value + captured) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:69:5
++ --> $DIR/option_map_unit_fn_fixable.rs:66:5
+ |
+LL | x.field.map(|value| { diverge(value + captured); });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { diverge(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:74:5
++ --> $DIR/option_map_unit_fn_fixable.rs:68:5
+ |
+LL | x.field.map(|value| { { diverge(value + captured); } });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { diverge(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:76:5
++ --> $DIR/option_map_unit_fn_fixable.rs:73:5
+ |
+LL | x.field.map(|value| { let y = plus_one(value + captured); });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { let y = plus_one(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:78:5
++ --> $DIR/option_map_unit_fn_fixable.rs:75:5
+ |
+LL | x.field.map(|value| { plus_one(value + captured); });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { plus_one(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:81:5
++ --> $DIR/option_map_unit_fn_fixable.rs:77:5
+ |
+LL | x.field.map(|value| { { plus_one(value + captured); } });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = x.field { plus_one(value + captured); }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:83:5
++ --> $DIR/option_map_unit_fn_fixable.rs:80:5
+ |
+LL | x.field.map(|ref value| { do_nothing(value + captured) });
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(ref value) = x.field { do_nothing(value + captured) }`
+
+error: called `map(f)` on an `Option` value where `f` is a function that returns the unit type `()`
- --> $DIR/option_map_unit_fn_fixable.rs:85:5
++ --> $DIR/option_map_unit_fn_fixable.rs:82:5
+ |
+LL | option().map(do_nothing);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(a) = option() { do_nothing(a) }`
+
+error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()`
++ --> $DIR/option_map_unit_fn_fixable.rs:84:5
+ |
+LL | option().map(|value| println!("{:?}", value));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
+ | |
+ | help: try this: `if let Some(value) = option() { println!("{:?}", value) }`
+
+error: aborting due to 19 previous errors
+
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::unnecessary_wraps, clippy::borrow_as_ptr)]
+#![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();
+ }
+}
+
+fn main() {}
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::unnecessary_wraps, clippy::borrow_as_ptr)]
+#![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());
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/or_fun_call.rs:49:22
+error: use of `unwrap_or` followed by a function call
- --> $DIR/or_fun_call.rs:52:14
++ --> $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:55:21
++ --> $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:58:14
++ --> $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:61:19
++ --> $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:64:24
++ --> $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:67:23
++ --> $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:70:18
++ --> $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:73:18
++ --> $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:76:14
++ --> $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:79:21
++ --> $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:82:19
++ --> $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:85:23
++ --> $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:88:21
++ --> $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:91:25
++ --> $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:94:21
++ --> $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:102:21
++ --> $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:104:21
++ --> $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:128:35
++ --> $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:167:14
++ --> $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:173:14
++ --> $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:175:14
++ --> $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:189:14
++ --> $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:202:14
++ --> $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:214:14
++ --> $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:225:10
++ --> $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: aborting due to 26 previous errors
+
--- /dev/null
- #![allow(clippy::unnecessary_wraps)]
+#![warn(clippy::panic_in_result_fn)]
++#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+struct A;
+
+impl A {
+ fn result_with_assert_with_message(x: i32) -> Result<bool, String> // should emit lint
+ {
+ assert!(x == 5, "wrong argument");
+ Ok(true)
+ }
+
+ fn result_with_assert_eq(x: i32) -> Result<bool, String> // should emit lint
+ {
+ assert_eq!(x, 5);
+ Ok(true)
+ }
+
+ fn result_with_assert_ne(x: i32) -> Result<bool, String> // should emit lint
+ {
+ assert_ne!(x, 1);
+ Ok(true)
+ }
+
+ fn other_with_assert_with_message(x: i32) // should not emit lint
+ {
+ assert!(x == 5, "wrong argument");
+ }
+
+ fn other_with_assert_eq(x: i32) // should not emit lint
+ {
+ assert_eq!(x, 5);
+ }
+
+ fn other_with_assert_ne(x: i32) // should not emit lint
+ {
+ assert_ne!(x, 1);
+ }
+
+ fn result_without_banned_functions() -> Result<bool, String> // should not emit lint
+ {
+ let assert = "assert!";
+ println!("No {}", assert);
+ Ok(true)
+ }
+}
+
+fn main() {}
--- /dev/null
- #![allow(clippy::unnecessary_wraps)]
+#![warn(clippy::panic_in_result_fn)]
++#![allow(clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+// debug_assert should never trigger the `panic_in_result_fn` lint
+
+struct A;
+
+impl A {
+ fn result_with_debug_assert_with_message(x: i32) -> Result<bool, String> {
+ debug_assert!(x == 5, "wrong argument");
+ Ok(true)
+ }
+
+ fn result_with_debug_assert_eq(x: i32) -> Result<bool, String> {
+ debug_assert_eq!(x, 5);
+ Ok(true)
+ }
+
+ fn result_with_debug_assert_ne(x: i32) -> Result<bool, String> {
+ debug_assert_ne!(x, 1);
+ Ok(true)
+ }
+
+ fn other_with_debug_assert_with_message(x: i32) {
+ debug_assert!(x == 5, "wrong argument");
+ }
+
+ fn other_with_debug_assert_eq(x: i32) {
+ debug_assert_eq!(x, 5);
+ }
+
+ fn other_with_debug_assert_ne(x: i32) {
+ debug_assert_ne!(x, 1);
+ }
+
+ fn result_without_banned_functions() -> Result<bool, String> {
+ let debug_assert = "debug_assert!";
+ println!("No {}", debug_assert);
+ Ok(true)
+ }
+}
+
+fn main() {}
--- /dev/null
- #![allow(unused)]
+// run-rustfix
+#![warn(clippy::all)]
++#![allow(unused)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ let v = Some(true);
+ let s = [0, 1, 2, 3, 4];
+ match v {
+ Some(x) => (),
+ y => (),
+ }
+ match v {
+ Some(x) => (),
+ y @ None => (), // no error
+ }
+ match s {
+ [x, inside @ .., y] => (), // no error
+ [..] => (),
+ }
+
+ let mut mutv = vec![1, 2, 3];
+
+ // required "ref" left out in suggestion: #5271
+ match mutv {
+ ref mut x => {
+ x.push(4);
+ println!("vec: {:?}", x);
+ },
+ ref y if y == &vec![0] => (),
+ }
+
+ match mutv {
+ ref x => println!("vec: {:?}", x),
+ ref y if y == &vec![0] => (),
+ }
+}
--- /dev/null
- #![allow(unused)]
+// run-rustfix
+#![warn(clippy::all)]
++#![allow(unused)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ let v = Some(true);
+ let s = [0, 1, 2, 3, 4];
+ match v {
+ Some(x) => (),
+ y @ _ => (),
+ }
+ match v {
+ Some(x) => (),
+ y @ None => (), // no error
+ }
+ match s {
+ [x, inside @ .., y] => (), // no error
+ [..] => (),
+ }
+
+ let mut mutv = vec![1, 2, 3];
+
+ // required "ref" left out in suggestion: #5271
+ match mutv {
+ ref mut x @ _ => {
+ x.push(4);
+ println!("vec: {:?}", x);
+ },
+ ref y if y == &vec![0] => (),
+ }
+
+ match mutv {
+ ref x @ _ => println!("vec: {:?}", x),
+ ref y if y == &vec![0] => (),
+ }
+}
--- /dev/null
- --> $DIR/patterns.rs:10:9
+error: the `y @ _` pattern can be written as just `y`
- --> $DIR/patterns.rs:25:9
++ --> $DIR/patterns.rs:11:9
+ |
+LL | y @ _ => (),
+ | ^^^^^ help: try: `y`
+ |
+ = note: `-D clippy::redundant-pattern` implied by `-D warnings`
+
+error: the `x @ _` pattern can be written as just `x`
- --> $DIR/patterns.rs:33:9
++ --> $DIR/patterns.rs:26:9
+ |
+LL | ref mut x @ _ => {
+ | ^^^^^^^^^^^^^ help: try: `ref mut x`
+
+error: the `x @ _` pattern can be written as just `x`
++ --> $DIR/patterns.rs:34:9
+ |
+LL | ref x @ _ => println!("vec: {:?}", x),
+ | ^^^^^^^^^ help: try: `ref x`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
+#![warn(clippy::print_literal)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ // these should be fine
+ print!("Hello");
+ println!("Hello");
+ let world = "world";
+ println!("Hello {}", world);
+ println!("Hello {world}", world = world);
+ println!("3 in hex is {:X}", 3);
+ println!("2 + 1 = {:.4}", 3);
+ println!("2 + 1 = {:5.4}", 3);
+ println!("Debug test {:?}", "hello, world");
+ println!("{0:8} {1:>8}", "hello", "world");
+ println!("{1:8} {0:>8}", "hello", "world");
+ println!("{foo:8} {bar:>8}", foo = "hello", bar = "world");
+ println!("{bar:8} {foo:>8}", foo = "hello", bar = "world");
+ println!("{number:>width$}", number = 1, width = 6);
+ println!("{number:>0width$}", number = 1, width = 6);
+ println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
+ println!("10 / 4 is {}", 2.5);
+ println!("2 + 1 = {}", 3);
+ println!("From expansion {}", stringify!(not a string literal));
+
+ // these should throw warnings
+ print!("Hello {}", "world");
+ println!("Hello {} {}", world, "world");
+ println!("Hello {}", "world");
+ println!("{} {:.4}", "a literal", 5);
+
+ // positional args don't change the fact
+ // that we're using a literal -- this should
+ // throw a warning
+ println!("{0} {1}", "hello", "world");
+ println!("{1} {0}", "hello", "world");
+
+ // named args shouldn't change anything either
+ println!("{foo} {bar}", foo = "hello", bar = "world");
+ println!("{bar} {foo}", foo = "hello", bar = "world");
+}
--- /dev/null
- --> $DIR/print_literal.rs:26:24
+error: literal with an empty format string
- --> $DIR/print_literal.rs:27:36
++ --> $DIR/print_literal.rs:27:24
+ |
+LL | print!("Hello {}", "world");
+ | ^^^^^^^
+ |
+ = note: `-D clippy::print-literal` implied by `-D warnings`
+help: try this
+ |
+LL - print!("Hello {}", "world");
+LL + print!("Hello world");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:28:26
++ --> $DIR/print_literal.rs:28:36
+ |
+LL | println!("Hello {} {}", world, "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("Hello {} {}", world, "world");
+LL + println!("Hello {} world", world);
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:29:26
++ --> $DIR/print_literal.rs:29:26
+ |
+LL | println!("Hello {}", "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("Hello {}", "world");
+LL + println!("Hello world");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:34:25
++ --> $DIR/print_literal.rs:30:26
+ |
+LL | println!("{} {:.4}", "a literal", 5);
+ | ^^^^^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{} {:.4}", "a literal", 5);
+LL + println!("a literal {:.4}", 5);
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:34:34
++ --> $DIR/print_literal.rs:35:25
+ |
+LL | println!("{0} {1}", "hello", "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{0} {1}", "hello", "world");
+LL + println!("hello {1}", "world");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:35:34
++ --> $DIR/print_literal.rs:35:34
+ |
+LL | println!("{0} {1}", "hello", "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{0} {1}", "hello", "world");
+LL + println!("{0} world", "hello");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:35:25
++ --> $DIR/print_literal.rs:36:34
+ |
+LL | println!("{1} {0}", "hello", "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{1} {0}", "hello", "world");
+LL + println!("world {0}", "hello");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:38:35
++ --> $DIR/print_literal.rs:36:25
+ |
+LL | println!("{1} {0}", "hello", "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{1} {0}", "hello", "world");
+LL + println!("{1} hello", "world");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:38:50
++ --> $DIR/print_literal.rs:39:35
+ |
+LL | println!("{foo} {bar}", foo = "hello", bar = "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{foo} {bar}", foo = "hello", bar = "world");
+LL + println!("hello {bar}", bar = "world");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:39:50
++ --> $DIR/print_literal.rs:39:50
+ |
+LL | println!("{foo} {bar}", foo = "hello", bar = "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{foo} {bar}", foo = "hello", bar = "world");
+LL + println!("{foo} world", foo = "hello");
+ |
+
+error: literal with an empty format string
- --> $DIR/print_literal.rs:39:35
++ --> $DIR/print_literal.rs:40:50
+ |
+LL | println!("{bar} {foo}", foo = "hello", bar = "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{bar} {foo}", foo = "hello", bar = "world");
+LL + println!("world {foo}", foo = "hello");
+ |
+
+error: literal with an empty format string
++ --> $DIR/print_literal.rs:40:35
+ |
+LL | println!("{bar} {foo}", foo = "hello", bar = "world");
+ | ^^^^^^^
+ |
+help: try this
+ |
+LL - println!("{bar} {foo}", foo = "hello", bar = "world");
+LL + println!("{bar} hello", bar = "world");
+ |
+
+error: aborting due to 12 previous errors
+
--- /dev/null
+// run-rustfix
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let vec = vec![b'a', b'b', b'c'];
+ let ptr = vec.as_ptr();
+
+ let offset_u8 = 1_u8;
+ let offset_usize = 1_usize;
+ let offset_isize = 1_isize;
+
+ unsafe {
+ let _ = ptr.add(offset_usize);
+ let _ = ptr.offset(offset_isize as isize);
+ let _ = ptr.offset(offset_u8 as isize);
+
+ let _ = ptr.wrapping_add(offset_usize);
+ let _ = ptr.wrapping_offset(offset_isize as isize);
+ let _ = ptr.wrapping_offset(offset_u8 as isize);
+ }
+}
--- /dev/null
+// run-rustfix
++#![allow(clippy::unnecessary_cast)]
+
+fn main() {
+ let vec = vec![b'a', b'b', b'c'];
+ let ptr = vec.as_ptr();
+
+ let offset_u8 = 1_u8;
+ let offset_usize = 1_usize;
+ let offset_isize = 1_isize;
+
+ unsafe {
+ let _ = ptr.offset(offset_usize as isize);
+ let _ = ptr.offset(offset_isize as isize);
+ let _ = ptr.offset(offset_u8 as isize);
+
+ let _ = ptr.wrapping_offset(offset_usize as isize);
+ let _ = ptr.wrapping_offset(offset_isize as isize);
+ let _ = ptr.wrapping_offset(offset_u8 as isize);
+ }
+}
--- /dev/null
- --> $DIR/ptr_offset_with_cast.rs:12:17
+error: use of `offset` with a `usize` casted to an `isize`
- --> $DIR/ptr_offset_with_cast.rs:16:17
++ --> $DIR/ptr_offset_with_cast.rs:13:17
+ |
+LL | let _ = ptr.offset(offset_usize as isize);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)`
+ |
+ = note: `-D clippy::ptr-offset-with-cast` implied by `-D warnings`
+
+error: use of `wrapping_offset` with a `usize` casted to an `isize`
++ --> $DIR/ptr_offset_with_cast.rs:17:17
+ |
+LL | let _ = ptr.wrapping_offset(offset_usize as isize);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)`
+
+error: aborting due to 2 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();
+ }
+
+ 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();
+ }
+
+ 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
- clippy::deref_addrof,
- clippy::borrow_deref_ref
+#![warn(clippy::recursive_format_impl)]
+#![allow(
++ clippy::borrow_deref_ref,
++ clippy::deref_addrof,
+ clippy::inherent_to_string_shadow_display,
+ clippy::to_string_in_format_args,
++ clippy::uninlined_format_args
+)]
+
+use std::fmt;
+
+struct A;
+impl A {
+ fn fmt(&self) {
+ self.to_string();
+ }
+}
+
+trait B {
+ fn fmt(&self) {}
+}
+
+impl B for A {
+ fn fmt(&self) {
+ self.to_string();
+ }
+}
+
+impl fmt::Display for A {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.to_string())
+ }
+}
+
+fn fmt(a: A) {
+ a.to_string();
+}
+
+struct C;
+
+impl C {
+ // Doesn't trigger if to_string defined separately
+ // i.e. not using ToString trait (from Display)
+ fn to_string(&self) -> String {
+ String::from("I am C")
+ }
+}
+
+impl fmt::Display for C {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.to_string())
+ }
+}
+
+enum D {
+ E(String),
+ F,
+}
+
+impl std::fmt::Display for D {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match &self {
+ Self::E(string) => write!(f, "E {}", string.to_string()),
+ Self::F => write!(f, "F"),
+ }
+ }
+}
+
+// Check for use of self as Display, in Display impl
+// Triggers on direct use of self
+struct G;
+
+impl std::fmt::Display for G {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self)
+ }
+}
+
+// Triggers on reference to self
+struct H;
+
+impl std::fmt::Display for H {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", &self)
+ }
+}
+
+impl std::fmt::Debug for H {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", &self)
+ }
+}
+
+// Triggers on multiple reference to self
+struct H2;
+
+impl std::fmt::Display for H2 {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", &&&self)
+ }
+}
+
+// Doesn't trigger on correct deref
+struct I;
+
+impl std::ops::Deref for I {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for I {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", &**self)
+ }
+}
+
+impl std::fmt::Debug for I {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:?}", &**self)
+ }
+}
+
+// Doesn't trigger on multiple correct deref
+struct I2;
+
+impl std::ops::Deref for I2 {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for I2 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", **&&&**self)
+ }
+}
+
+// Doesn't trigger on multiple correct deref
+struct I3;
+
+impl std::ops::Deref for I3 {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for I3 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", &&**&&&**self)
+ }
+}
+
+// Does trigger when deref resolves to self
+struct J;
+
+impl std::ops::Deref for J {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for J {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", &*self)
+ }
+}
+
+impl std::fmt::Debug for J {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:?}", &*self)
+ }
+}
+
+struct J2;
+
+impl std::ops::Deref for J2 {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for J2 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", *self)
+ }
+}
+
+struct J3;
+
+impl std::ops::Deref for J3 {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for J3 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", **&&*self)
+ }
+}
+
+struct J4;
+
+impl std::ops::Deref for J4 {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ "test"
+ }
+}
+
+impl std::fmt::Display for J4 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", &&**&&*self)
+ }
+}
+
+// Doesn't trigger on Debug from Display
+struct K;
+
+impl std::fmt::Debug for K {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "test")
+ }
+}
+
+impl std::fmt::Display for K {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
+// Doesn't trigger on Display from Debug
+struct K2;
+
+impl std::fmt::Debug for K2 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", self)
+ }
+}
+
+impl std::fmt::Display for K2 {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "test")
+ }
+}
+
+// Doesn't trigger on struct fields
+struct L {
+ field1: u32,
+ field2: i32,
+}
+
+impl std::fmt::Display for L {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{},{}", self.field1, self.field2)
+ }
+}
+
+impl std::fmt::Debug for L {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:?},{:?}", self.field1, self.field2)
+ }
+}
+
+// Doesn't trigger on nested enum matching
+enum Tree {
+ Leaf,
+ Node(Vec<Tree>),
+}
+
+impl std::fmt::Display for Tree {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Tree::Leaf => write!(f, "*"),
+ Tree::Node(children) => {
+ write!(f, "(")?;
+ for child in children.iter() {
+ write!(f, "{},", child)?;
+ }
+ write!(f, ")")
+ },
+ }
+ }
+}
+
+impl std::fmt::Debug for Tree {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Tree::Leaf => write!(f, "*"),
+ Tree::Node(children) => {
+ write!(f, "(")?;
+ for child in children.iter() {
+ write!(f, "{:?},", child)?;
+ }
+ write!(f, ")")
+ },
+ }
+ }
+}
+
+fn main() {
+ let a = A;
+ a.to_string();
+ a.fmt();
+ fmt(a);
+
+ let c = C;
+ c.to_string();
+}
--- /dev/null
- --> $DIR/recursive_format_impl.rs:30:25
+error: using `self.to_string` in `fmt::Display` implementation will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:74:9
++ --> $DIR/recursive_format_impl.rs:31:25
+ |
+LL | write!(f, "{}", self.to_string())
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::recursive-format-impl` implied by `-D warnings`
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:83:9
++ --> $DIR/recursive_format_impl.rs:75:9
+ |
+LL | write!(f, "{}", self)
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:89:9
++ --> $DIR/recursive_format_impl.rs:84:9
+ |
+LL | write!(f, "{}", &self)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Debug` in `impl Debug` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:98:9
++ --> $DIR/recursive_format_impl.rs:90:9
+ |
+LL | write!(f, "{:?}", &self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:172:9
++ --> $DIR/recursive_format_impl.rs:99:9
+ |
+LL | write!(f, "{}", &&&self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:178:9
++ --> $DIR/recursive_format_impl.rs:173:9
+ |
+LL | write!(f, "{}", &*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Debug` in `impl Debug` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:194:9
++ --> $DIR/recursive_format_impl.rs:179:9
+ |
+LL | write!(f, "{:?}", &*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:210:9
++ --> $DIR/recursive_format_impl.rs:195:9
+ |
+LL | write!(f, "{}", *self)
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
- --> $DIR/recursive_format_impl.rs:226:9
++ --> $DIR/recursive_format_impl.rs:211:9
+ |
+LL | write!(f, "{}", **&&*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: using `self` as `Display` in `impl Display` will cause infinite recursion
++ --> $DIR/recursive_format_impl.rs:227:9
+ |
+LL | write!(f, "{}", &&**&&*self)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 10 previous errors
+
--- /dev/null
-
+// run-rustfix
+// rustfix-only-machine-applicable
- #![allow(clippy::implicit_clone, clippy::drop_non_drop)]
+#![feature(lint_reasons)]
++#![allow(clippy::drop_non_drop, clippy::implicit_clone, clippy::uninlined_format_args)]
++
+use std::ffi::OsString;
+use std::path::Path;
+
+fn main() {
+ let _s = ["lorem", "ipsum"].join(" ");
+
+ let s = String::from("foo");
+ let _s = s;
+
+ let s = String::from("foo");
+ let _s = s;
+
+ let s = String::from("foo");
+ let _s = s;
+
+ let _s = Path::new("/a/b/").join("c");
+
+ let _s = Path::new("/a/b/").join("c");
+
+ let _s = OsString::new();
+
+ let _s = OsString::new();
+
+ // Check that lint level works
+ #[allow(clippy::redundant_clone)]
+ let _s = String::new().to_string();
+
+ // Check that lint level works
+ #[expect(clippy::redundant_clone)]
+ let _s = String::new().to_string();
+
+ let tup = (String::from("foo"),);
+ let _t = tup.0;
+
+ let tup_ref = &(String::from("foo"),);
+ let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed
+
+ {
+ let x = String::new();
+ let y = &x;
+
+ let _x = x.clone(); // ok; `x` is borrowed by `y`
+
+ let _ = y.len();
+ }
+
+ let x = (String::new(),);
+ let _ = Some(String::new()).unwrap_or_else(|| x.0.clone()); // ok; closure borrows `x`
+
+ with_branch(Alpha, true);
+ cannot_double_move(Alpha);
+ cannot_move_from_type_with_drop();
+ borrower_propagation();
+ not_consumed();
+ issue_5405();
+ manually_drop();
+ clone_then_move_cloned();
+ hashmap_neg();
+ false_negative_5707();
+}
+
+#[derive(Clone)]
+struct Alpha;
+fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) {
+ if b { (a.clone(), a) } else { (Alpha, a) }
+}
+
+fn cannot_double_move(a: Alpha) -> (Alpha, Alpha) {
+ (a.clone(), a)
+}
+
+struct TypeWithDrop {
+ x: String,
+}
+
+impl Drop for TypeWithDrop {
+ fn drop(&mut self) {}
+}
+
+fn cannot_move_from_type_with_drop() -> String {
+ let s = TypeWithDrop { x: String::new() };
+ s.x.clone() // removing this `clone()` summons E0509
+}
+
+fn borrower_propagation() {
+ let s = String::new();
+ let t = String::new();
+
+ {
+ fn b() -> bool {
+ unimplemented!()
+ }
+ let _u = if b() { &s } else { &t };
+
+ // ok; `s` and `t` are possibly borrowed
+ let _s = s.clone();
+ let _t = t.clone();
+ }
+
+ {
+ let _u = || s.len();
+ let _v = [&t; 32];
+ let _s = s.clone(); // ok
+ let _t = t.clone(); // ok
+ }
+
+ {
+ let _u = {
+ let u = Some(&s);
+ let _ = s.clone(); // ok
+ u
+ };
+ let _s = s.clone(); // ok
+ }
+
+ {
+ use std::convert::identity as id;
+ let _u = id(id(&s));
+ let _s = s.clone(); // ok, `u` borrows `s`
+ }
+
+ let _s = s;
+ let _t = t;
+
+ #[derive(Clone)]
+ struct Foo {
+ x: usize,
+ }
+
+ {
+ let f = Foo { x: 123 };
+ let _x = Some(f.x);
+ let _f = f;
+ }
+
+ {
+ let f = Foo { x: 123 };
+ let _x = &f.x;
+ let _f = f.clone(); // ok
+ }
+}
+
+fn not_consumed() {
+ let x = std::path::PathBuf::from("home");
+ let y = x.join("matthias");
+ // join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is
+ // redundant. (It also does not consume the PathBuf)
+
+ println!("x: {:?}, y: {:?}", x, y);
+
+ let mut s = String::new();
+ s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior.
+ s.push_str("bar");
+ assert_eq!(s, "bar");
+
+ let t = Some(s);
+ // OK
+ if let Some(x) = t.clone() {
+ println!("{}", x);
+ }
+ if let Some(x) = t {
+ println!("{}", x);
+ }
+}
+
+#[allow(clippy::clone_on_copy)]
+fn issue_5405() {
+ let a: [String; 1] = [String::from("foo")];
+ let _b: String = a[0].clone();
+
+ let c: [usize; 2] = [2, 3];
+ let _d: usize = c[1].clone();
+}
+
+fn manually_drop() {
+ use std::mem::ManuallyDrop;
+ use std::sync::Arc;
+
+ let a = ManuallyDrop::new(Arc::new("Hello!".to_owned()));
+ let _ = a.clone(); // OK
+
+ let p: *const String = Arc::into_raw(ManuallyDrop::into_inner(a));
+ unsafe {
+ Arc::from_raw(p);
+ Arc::from_raw(p);
+ }
+}
+
+fn clone_then_move_cloned() {
+ // issue #5973
+ let x = Some(String::new());
+ // ok, x is moved while the clone is in use.
+ assert_eq!(x.clone(), None, "not equal {}", x.unwrap());
+
+ // issue #5595
+ fn foo<F: Fn()>(_: &Alpha, _: F) {}
+ let x = Alpha;
+ // ok, data is moved while the clone is in use.
+ foo(&x, move || {
+ let _ = x;
+ });
+
+ // issue #6998
+ struct S(String);
+ impl S {
+ fn m(&mut self) {}
+ }
+ let mut x = S(String::new());
+ x.0.clone().chars().for_each(|_| x.m());
+}
+
+fn hashmap_neg() {
+ // issue 5707
+ use std::collections::HashMap;
+ use std::path::PathBuf;
+
+ let p = PathBuf::from("/");
+
+ let mut h: HashMap<&str, &str> = HashMap::new();
+ h.insert("orig-p", p.to_str().unwrap());
+
+ let mut q = p.clone();
+ q.push("foo");
+
+ println!("{:?} {}", h, q.display());
+}
+
+fn false_negative_5707() {
+ fn foo(_x: &Alpha, _y: &mut Alpha) {}
+
+ let x = Alpha;
+ let mut y = Alpha;
+ foo(&x, &mut y);
+ let _z = x.clone(); // pr 7346 can't lint on `x`
+ drop(y);
+}
--- /dev/null
-
+// run-rustfix
+// rustfix-only-machine-applicable
- #![allow(clippy::implicit_clone, clippy::drop_non_drop)]
+#![feature(lint_reasons)]
++#![allow(clippy::drop_non_drop, clippy::implicit_clone, clippy::uninlined_format_args)]
++
+use std::ffi::OsString;
+use std::path::Path;
+
+fn main() {
+ let _s = ["lorem", "ipsum"].join(" ").to_string();
+
+ let s = String::from("foo");
+ let _s = s.clone();
+
+ let s = String::from("foo");
+ let _s = s.to_string();
+
+ let s = String::from("foo");
+ let _s = s.to_owned();
+
+ let _s = Path::new("/a/b/").join("c").to_owned();
+
+ let _s = Path::new("/a/b/").join("c").to_path_buf();
+
+ let _s = OsString::new().to_owned();
+
+ let _s = OsString::new().to_os_string();
+
+ // Check that lint level works
+ #[allow(clippy::redundant_clone)]
+ let _s = String::new().to_string();
+
+ // Check that lint level works
+ #[expect(clippy::redundant_clone)]
+ let _s = String::new().to_string();
+
+ let tup = (String::from("foo"),);
+ let _t = tup.0.clone();
+
+ let tup_ref = &(String::from("foo"),);
+ let _s = tup_ref.0.clone(); // this `.clone()` cannot be removed
+
+ {
+ let x = String::new();
+ let y = &x;
+
+ let _x = x.clone(); // ok; `x` is borrowed by `y`
+
+ let _ = y.len();
+ }
+
+ let x = (String::new(),);
+ let _ = Some(String::new()).unwrap_or_else(|| x.0.clone()); // ok; closure borrows `x`
+
+ with_branch(Alpha, true);
+ cannot_double_move(Alpha);
+ cannot_move_from_type_with_drop();
+ borrower_propagation();
+ not_consumed();
+ issue_5405();
+ manually_drop();
+ clone_then_move_cloned();
+ hashmap_neg();
+ false_negative_5707();
+}
+
+#[derive(Clone)]
+struct Alpha;
+fn with_branch(a: Alpha, b: bool) -> (Alpha, Alpha) {
+ if b { (a.clone(), a.clone()) } else { (Alpha, a) }
+}
+
+fn cannot_double_move(a: Alpha) -> (Alpha, Alpha) {
+ (a.clone(), a)
+}
+
+struct TypeWithDrop {
+ x: String,
+}
+
+impl Drop for TypeWithDrop {
+ fn drop(&mut self) {}
+}
+
+fn cannot_move_from_type_with_drop() -> String {
+ let s = TypeWithDrop { x: String::new() };
+ s.x.clone() // removing this `clone()` summons E0509
+}
+
+fn borrower_propagation() {
+ let s = String::new();
+ let t = String::new();
+
+ {
+ fn b() -> bool {
+ unimplemented!()
+ }
+ let _u = if b() { &s } else { &t };
+
+ // ok; `s` and `t` are possibly borrowed
+ let _s = s.clone();
+ let _t = t.clone();
+ }
+
+ {
+ let _u = || s.len();
+ let _v = [&t; 32];
+ let _s = s.clone(); // ok
+ let _t = t.clone(); // ok
+ }
+
+ {
+ let _u = {
+ let u = Some(&s);
+ let _ = s.clone(); // ok
+ u
+ };
+ let _s = s.clone(); // ok
+ }
+
+ {
+ use std::convert::identity as id;
+ let _u = id(id(&s));
+ let _s = s.clone(); // ok, `u` borrows `s`
+ }
+
+ let _s = s.clone();
+ let _t = t.clone();
+
+ #[derive(Clone)]
+ struct Foo {
+ x: usize,
+ }
+
+ {
+ let f = Foo { x: 123 };
+ let _x = Some(f.x);
+ let _f = f.clone();
+ }
+
+ {
+ let f = Foo { x: 123 };
+ let _x = &f.x;
+ let _f = f.clone(); // ok
+ }
+}
+
+fn not_consumed() {
+ let x = std::path::PathBuf::from("home");
+ let y = x.clone().join("matthias");
+ // join() creates a new owned PathBuf, does not take a &mut to x variable, thus the .clone() is
+ // redundant. (It also does not consume the PathBuf)
+
+ println!("x: {:?}, y: {:?}", x, y);
+
+ let mut s = String::new();
+ s.clone().push_str("foo"); // OK, removing this `clone()` will change the behavior.
+ s.push_str("bar");
+ assert_eq!(s, "bar");
+
+ let t = Some(s);
+ // OK
+ if let Some(x) = t.clone() {
+ println!("{}", x);
+ }
+ if let Some(x) = t {
+ println!("{}", x);
+ }
+}
+
+#[allow(clippy::clone_on_copy)]
+fn issue_5405() {
+ let a: [String; 1] = [String::from("foo")];
+ let _b: String = a[0].clone();
+
+ let c: [usize; 2] = [2, 3];
+ let _d: usize = c[1].clone();
+}
+
+fn manually_drop() {
+ use std::mem::ManuallyDrop;
+ use std::sync::Arc;
+
+ let a = ManuallyDrop::new(Arc::new("Hello!".to_owned()));
+ let _ = a.clone(); // OK
+
+ let p: *const String = Arc::into_raw(ManuallyDrop::into_inner(a));
+ unsafe {
+ Arc::from_raw(p);
+ Arc::from_raw(p);
+ }
+}
+
+fn clone_then_move_cloned() {
+ // issue #5973
+ let x = Some(String::new());
+ // ok, x is moved while the clone is in use.
+ assert_eq!(x.clone(), None, "not equal {}", x.unwrap());
+
+ // issue #5595
+ fn foo<F: Fn()>(_: &Alpha, _: F) {}
+ let x = Alpha;
+ // ok, data is moved while the clone is in use.
+ foo(&x.clone(), move || {
+ let _ = x;
+ });
+
+ // issue #6998
+ struct S(String);
+ impl S {
+ fn m(&mut self) {}
+ }
+ let mut x = S(String::new());
+ x.0.clone().chars().for_each(|_| x.m());
+}
+
+fn hashmap_neg() {
+ // issue 5707
+ use std::collections::HashMap;
+ use std::path::PathBuf;
+
+ let p = PathBuf::from("/");
+
+ let mut h: HashMap<&str, &str> = HashMap::new();
+ h.insert("orig-p", p.to_str().unwrap());
+
+ let mut q = p.clone();
+ q.push("foo");
+
+ println!("{:?} {}", h, q.display());
+}
+
+fn false_negative_5707() {
+ fn foo(_x: &Alpha, _y: &mut Alpha) {}
+
+ let x = Alpha;
+ let mut y = Alpha;
+ foo(&x, &mut y);
+ let _z = x.clone(); // pr 7346 can't lint on `x`
+ drop(y);
+}
--- /dev/null
-
- #![warn(clippy::all)]
- #![warn(clippy::redundant_pattern_matching)]
- #![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)]
+// run-rustfix
++#![warn(clippy::all, clippy::redundant_pattern_matching)]
++#![allow(unused_must_use)]
++#![allow(
++ clippy::match_like_matches_macro,
++ clippy::needless_bool,
++ clippy::uninlined_format_args
++)]
+
+use std::net::{
+ IpAddr::{self, V4, V6},
+ Ipv4Addr, Ipv6Addr,
+};
+
+fn main() {
+ let ipaddr: IpAddr = V4(Ipv4Addr::LOCALHOST);
+ if ipaddr.is_ipv4() {}
+
+ if V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+
+ if V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+
+ while V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+
+ while V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+
+ if V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+
+ if V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+
+ if let V4(ipaddr) = V4(Ipv4Addr::LOCALHOST) {
+ println!("{}", ipaddr);
+ }
+
+ V4(Ipv4Addr::LOCALHOST).is_ipv4();
+
+ V4(Ipv4Addr::LOCALHOST).is_ipv6();
+
+ V6(Ipv6Addr::LOCALHOST).is_ipv6();
+
+ V6(Ipv6Addr::LOCALHOST).is_ipv4();
+
+ let _ = if V4(Ipv4Addr::LOCALHOST).is_ipv4() {
+ true
+ } else {
+ false
+ };
+
+ ipaddr_const();
+
+ let _ = if gen_ipaddr().is_ipv4() {
+ 1
+ } else if gen_ipaddr().is_ipv6() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_ipaddr() -> IpAddr {
+ V4(Ipv4Addr::LOCALHOST)
+}
+
+const fn ipaddr_const() {
+ if V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+
+ if V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+
+ while V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+
+ while V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+
+ V4(Ipv4Addr::LOCALHOST).is_ipv4();
+
+ V6(Ipv6Addr::LOCALHOST).is_ipv6();
+}
--- /dev/null
-
- #![warn(clippy::all)]
- #![warn(clippy::redundant_pattern_matching)]
- #![allow(unused_must_use, clippy::needless_bool, clippy::match_like_matches_macro)]
+// run-rustfix
++#![warn(clippy::all, clippy::redundant_pattern_matching)]
++#![allow(unused_must_use)]
++#![allow(
++ clippy::match_like_matches_macro,
++ clippy::needless_bool,
++ clippy::uninlined_format_args
++)]
+
+use std::net::{
+ IpAddr::{self, V4, V6},
+ Ipv4Addr, Ipv6Addr,
+};
+
+fn main() {
+ let ipaddr: IpAddr = V4(Ipv4Addr::LOCALHOST);
+ if let V4(_) = &ipaddr {}
+
+ if let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+
+ if let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+
+ while let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+
+ while let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+
+ if V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+
+ if V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+
+ if let V4(ipaddr) = V4(Ipv4Addr::LOCALHOST) {
+ println!("{}", ipaddr);
+ }
+
+ match V4(Ipv4Addr::LOCALHOST) {
+ V4(_) => true,
+ V6(_) => false,
+ };
+
+ match V4(Ipv4Addr::LOCALHOST) {
+ V4(_) => false,
+ V6(_) => true,
+ };
+
+ match V6(Ipv6Addr::LOCALHOST) {
+ V4(_) => false,
+ V6(_) => true,
+ };
+
+ match V6(Ipv6Addr::LOCALHOST) {
+ V4(_) => true,
+ V6(_) => false,
+ };
+
+ let _ = if let V4(_) = V4(Ipv4Addr::LOCALHOST) {
+ true
+ } else {
+ false
+ };
+
+ ipaddr_const();
+
+ let _ = if let V4(_) = gen_ipaddr() {
+ 1
+ } else if let V6(_) = gen_ipaddr() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_ipaddr() -> IpAddr {
+ V4(Ipv4Addr::LOCALHOST)
+}
+
+const fn ipaddr_const() {
+ if let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+
+ if let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+
+ while let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+
+ while let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+
+ match V4(Ipv4Addr::LOCALHOST) {
+ V4(_) => true,
+ V6(_) => false,
+ };
+
+ match V6(Ipv6Addr::LOCALHOST) {
+ V4(_) => false,
+ V6(_) => true,
+ };
+}
--- /dev/null
- --> $DIR/redundant_pattern_matching_ipaddr.rs:14:12
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:16:12
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:17:12
+ |
+LL | if let V4(_) = &ipaddr {}
+ | -------^^^^^---------- help: try this: `if ipaddr.is_ipv4()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:18:12
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:19:12
+ |
+LL | if let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+ | -------^^^^^-------------------------- help: try this: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:20:15
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:21:12
+ |
+LL | if let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+ | -------^^^^^-------------------------- help: try this: `if V6(Ipv6Addr::LOCALHOST).is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:22:15
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:23:15
+ |
+LL | while let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+ | ----------^^^^^-------------------------- help: try this: `while V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:32:5
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:25:15
+ |
+LL | while let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+ | ----------^^^^^-------------------------- help: try this: `while V6(Ipv6Addr::LOCALHOST).is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:37:5
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:35:5
+ |
+LL | / match V4(Ipv4Addr::LOCALHOST) {
+LL | | V4(_) => true,
+LL | | V6(_) => false,
+LL | | };
+ | |_____^ help: try this: `V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:42:5
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:40:5
+ |
+LL | / match V4(Ipv4Addr::LOCALHOST) {
+LL | | V4(_) => false,
+LL | | V6(_) => true,
+LL | | };
+ | |_____^ help: try this: `V4(Ipv4Addr::LOCALHOST).is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:47:5
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:45:5
+ |
+LL | / match V6(Ipv6Addr::LOCALHOST) {
+LL | | V4(_) => false,
+LL | | V6(_) => true,
+LL | | };
+ | |_____^ help: try this: `V6(Ipv6Addr::LOCALHOST).is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:52:20
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:50:5
+ |
+LL | / match V6(Ipv6Addr::LOCALHOST) {
+LL | | V4(_) => true,
+LL | | V6(_) => false,
+LL | | };
+ | |_____^ help: try this: `V6(Ipv6Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:60:20
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:55:20
+ |
+LL | let _ = if let V4(_) = V4(Ipv4Addr::LOCALHOST) {
+ | -------^^^^^-------------------------- help: try this: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:62:19
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:63:20
+ |
+LL | let _ = if let V4(_) = gen_ipaddr() {
+ | -------^^^^^--------------- help: try this: `if gen_ipaddr().is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:74:12
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:65:19
+ |
+LL | } else if let V6(_) = gen_ipaddr() {
+ | -------^^^^^--------------- help: try this: `if gen_ipaddr().is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:76:12
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:77:12
+ |
+LL | if let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+ | -------^^^^^-------------------------- help: try this: `if V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:78:15
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:79:12
+ |
+LL | if let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+ | -------^^^^^-------------------------- help: try this: `if V6(Ipv6Addr::LOCALHOST).is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:80:15
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:81:15
+ |
+LL | while let V4(_) = V4(Ipv4Addr::LOCALHOST) {}
+ | ----------^^^^^-------------------------- help: try this: `while V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:82:5
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:83:15
+ |
+LL | while let V6(_) = V6(Ipv6Addr::LOCALHOST) {}
+ | ----------^^^^^-------------------------- help: try this: `while V6(Ipv6Addr::LOCALHOST).is_ipv6()`
+
+error: redundant pattern matching, consider using `is_ipv4()`
- --> $DIR/redundant_pattern_matching_ipaddr.rs:87:5
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:85:5
+ |
+LL | / match V4(Ipv4Addr::LOCALHOST) {
+LL | | V4(_) => true,
+LL | | V6(_) => false,
+LL | | };
+ | |_____^ help: try this: `V4(Ipv4Addr::LOCALHOST).is_ipv4()`
+
+error: redundant pattern matching, consider using `is_ipv6()`
++ --> $DIR/redundant_pattern_matching_ipaddr.rs:90:5
+ |
+LL | / match V6(Ipv6Addr::LOCALHOST) {
+LL | | V4(_) => false,
+LL | | V6(_) => true,
+LL | | };
+ | |_____^ help: try this: `V6(Ipv6Addr::LOCALHOST).is_ipv6()`
+
+error: aborting due to 18 previous errors
+
--- /dev/null
-
+// run-rustfix
- unused_must_use,
- clippy::needless_bool,
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
++#![allow(deprecated, unused_must_use)]
+#![allow(
- clippy::unnecessary_wraps,
- deprecated,
- clippy::if_same_then_else
++ clippy::if_same_then_else,
+ clippy::match_like_matches_macro,
++ clippy::needless_bool,
++ clippy::uninlined_format_args,
++ clippy::unnecessary_wraps
+)]
+
+fn main() {
+ let result: Result<usize, usize> = Err(5);
+ if result.is_ok() {}
+
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ while Ok::<i32, i32>(10).is_ok() {}
+
+ while Ok::<i32, i32>(10).is_err() {}
+
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ if let Ok(x) = Ok::<i32, i32>(42) {
+ println!("{}", x);
+ }
+
+ Ok::<i32, i32>(42).is_ok();
+
+ Ok::<i32, i32>(42).is_err();
+
+ Err::<i32, i32>(42).is_err();
+
+ Err::<i32, i32>(42).is_ok();
+
+ let _ = if Ok::<usize, ()>(4).is_ok() { true } else { false };
+
+ issue5504();
+ issue6067();
+ issue6065();
+
+ let _ = if gen_res().is_ok() {
+ 1
+ } else if gen_res().is_err() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_res() -> Result<(), ()> {
+ Ok(())
+}
+
+macro_rules! m {
+ () => {
+ Some(42u32)
+ };
+}
+
+fn issue5504() {
+ fn result_opt() -> Result<Option<i32>, i32> {
+ Err(42)
+ }
+
+ fn try_result_opt() -> Result<i32, i32> {
+ while (r#try!(result_opt())).is_some() {}
+ if (r#try!(result_opt())).is_some() {}
+ Ok(42)
+ }
+
+ try_result_opt();
+
+ if m!().is_some() {}
+ while m!().is_some() {}
+}
+
+fn issue6065() {
+ macro_rules! if_let_in_macro {
+ ($pat:pat, $x:expr) => {
+ if let Some($pat) = $x {}
+ };
+ }
+
+ // shouldn't be linted
+ if_let_in_macro!(_, Some(42));
+}
+
+// Methods that are unstable const should not be suggested within a const context, see issue #5697.
+// However, in Rust 1.48.0 the methods `is_ok` and `is_err` of `Result` were stabilized as const,
+// so the following should be linted.
+const fn issue6067() {
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ while Ok::<i32, i32>(10).is_ok() {}
+
+ while Ok::<i32, i32>(10).is_err() {}
+
+ Ok::<i32, i32>(42).is_ok();
+
+ Err::<i32, i32>(42).is_err();
+}
--- /dev/null
-
+// run-rustfix
- unused_must_use,
- clippy::needless_bool,
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
++#![allow(deprecated, unused_must_use)]
+#![allow(
- clippy::unnecessary_wraps,
- deprecated,
- clippy::if_same_then_else
++ clippy::if_same_then_else,
+ clippy::match_like_matches_macro,
++ clippy::needless_bool,
++ clippy::uninlined_format_args,
++ clippy::unnecessary_wraps
+)]
+
+fn main() {
+ let result: Result<usize, usize> = Err(5);
+ if let Ok(_) = &result {}
+
+ if let Ok(_) = Ok::<i32, i32>(42) {}
+
+ if let Err(_) = Err::<i32, i32>(42) {}
+
+ while let Ok(_) = Ok::<i32, i32>(10) {}
+
+ while let Err(_) = Ok::<i32, i32>(10) {}
+
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ if let Ok(x) = Ok::<i32, i32>(42) {
+ println!("{}", x);
+ }
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ let _ = if let Ok(_) = Ok::<usize, ()>(4) { true } else { false };
+
+ issue5504();
+ issue6067();
+ issue6065();
+
+ let _ = if let Ok(_) = gen_res() {
+ 1
+ } else if let Err(_) = gen_res() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_res() -> Result<(), ()> {
+ Ok(())
+}
+
+macro_rules! m {
+ () => {
+ Some(42u32)
+ };
+}
+
+fn issue5504() {
+ fn result_opt() -> Result<Option<i32>, i32> {
+ Err(42)
+ }
+
+ fn try_result_opt() -> Result<i32, i32> {
+ while let Some(_) = r#try!(result_opt()) {}
+ if let Some(_) = r#try!(result_opt()) {}
+ Ok(42)
+ }
+
+ try_result_opt();
+
+ if let Some(_) = m!() {}
+ while let Some(_) = m!() {}
+}
+
+fn issue6065() {
+ macro_rules! if_let_in_macro {
+ ($pat:pat, $x:expr) => {
+ if let Some($pat) = $x {}
+ };
+ }
+
+ // shouldn't be linted
+ if_let_in_macro!(_, Some(42));
+}
+
+// Methods that are unstable const should not be suggested within a const context, see issue #5697.
+// However, in Rust 1.48.0 the methods `is_ok` and `is_err` of `Result` were stabilized as const,
+// so the following should be linted.
+const fn issue6067() {
+ if let Ok(_) = Ok::<i32, i32>(42) {}
+
+ if let Err(_) = Err::<i32, i32>(42) {}
+
+ while let Ok(_) = Ok::<i32, i32>(10) {}
+
+ while let Err(_) = Ok::<i32, i32>(10) {}
+
+ match Ok::<i32, i32>(42) {
+ Ok(_) => true,
+ Err(_) => false,
+ };
+
+ match Err::<i32, i32>(42) {
+ Ok(_) => false,
+ Err(_) => true,
+ };
+}
--- /dev/null
- --> $DIR/redundant_pattern_matching_result.rs:16:12
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:18:12
++ --> $DIR/redundant_pattern_matching_result.rs:15:12
+ |
+LL | if let Ok(_) = &result {}
+ | -------^^^^^---------- help: try this: `if result.is_ok()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:20:12
++ --> $DIR/redundant_pattern_matching_result.rs:17:12
+ |
+LL | if let Ok(_) = Ok::<i32, i32>(42) {}
+ | -------^^^^^--------------------- help: try this: `if Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:22:15
++ --> $DIR/redundant_pattern_matching_result.rs:19:12
+ |
+LL | if let Err(_) = Err::<i32, i32>(42) {}
+ | -------^^^^^^---------------------- help: try this: `if Err::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:24:15
++ --> $DIR/redundant_pattern_matching_result.rs:21:15
+ |
+LL | while let Ok(_) = Ok::<i32, i32>(10) {}
+ | ----------^^^^^--------------------- help: try this: `while Ok::<i32, i32>(10).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:34:5
++ --> $DIR/redundant_pattern_matching_result.rs:23:15
+ |
+LL | while let Err(_) = Ok::<i32, i32>(10) {}
+ | ----------^^^^^^--------------------- help: try this: `while Ok::<i32, i32>(10).is_err()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:39:5
++ --> $DIR/redundant_pattern_matching_result.rs:33:5
+ |
+LL | / match Ok::<i32, i32>(42) {
+LL | | Ok(_) => true,
+LL | | Err(_) => false,
+LL | | };
+ | |_____^ help: try this: `Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:44:5
++ --> $DIR/redundant_pattern_matching_result.rs:38:5
+ |
+LL | / match Ok::<i32, i32>(42) {
+LL | | Ok(_) => false,
+LL | | Err(_) => true,
+LL | | };
+ | |_____^ help: try this: `Ok::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:49:5
++ --> $DIR/redundant_pattern_matching_result.rs:43:5
+ |
+LL | / match Err::<i32, i32>(42) {
+LL | | Ok(_) => false,
+LL | | Err(_) => true,
+LL | | };
+ | |_____^ help: try this: `Err::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:54:20
++ --> $DIR/redundant_pattern_matching_result.rs:48:5
+ |
+LL | / match Err::<i32, i32>(42) {
+LL | | Ok(_) => true,
+LL | | Err(_) => false,
+LL | | };
+ | |_____^ help: try this: `Err::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:60:20
++ --> $DIR/redundant_pattern_matching_result.rs:53:20
+ |
+LL | let _ = if let Ok(_) = Ok::<usize, ()>(4) { true } else { false };
+ | -------^^^^^--------------------- help: try this: `if Ok::<usize, ()>(4).is_ok()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:62:19
++ --> $DIR/redundant_pattern_matching_result.rs:59:20
+ |
+LL | let _ = if let Ok(_) = gen_res() {
+ | -------^^^^^------------ help: try this: `if gen_res().is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:85:19
++ --> $DIR/redundant_pattern_matching_result.rs:61:19
+ |
+LL | } else if let Err(_) = gen_res() {
+ | -------^^^^^^------------ help: try this: `if gen_res().is_err()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching_result.rs:86:16
++ --> $DIR/redundant_pattern_matching_result.rs:84:19
+ |
+LL | while let Some(_) = r#try!(result_opt()) {}
+ | ----------^^^^^^^----------------------- help: try this: `while (r#try!(result_opt())).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching_result.rs:92:12
++ --> $DIR/redundant_pattern_matching_result.rs:85:16
+ |
+LL | if let Some(_) = r#try!(result_opt()) {}
+ | -------^^^^^^^----------------------- help: try this: `if (r#try!(result_opt())).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching_result.rs:93:15
++ --> $DIR/redundant_pattern_matching_result.rs:91:12
+ |
+LL | if let Some(_) = m!() {}
+ | -------^^^^^^^------- help: try this: `if m!().is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
- --> $DIR/redundant_pattern_matching_result.rs:111:12
++ --> $DIR/redundant_pattern_matching_result.rs:92:15
+ |
+LL | while let Some(_) = m!() {}
+ | ----------^^^^^^^------- help: try this: `while m!().is_some()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:113:12
++ --> $DIR/redundant_pattern_matching_result.rs:110:12
+ |
+LL | if let Ok(_) = Ok::<i32, i32>(42) {}
+ | -------^^^^^--------------------- help: try this: `if Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:115:15
++ --> $DIR/redundant_pattern_matching_result.rs:112:12
+ |
+LL | if let Err(_) = Err::<i32, i32>(42) {}
+ | -------^^^^^^---------------------- help: try this: `if Err::<i32, i32>(42).is_err()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:117:15
++ --> $DIR/redundant_pattern_matching_result.rs:114:15
+ |
+LL | while let Ok(_) = Ok::<i32, i32>(10) {}
+ | ----------^^^^^--------------------- help: try this: `while Ok::<i32, i32>(10).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
- --> $DIR/redundant_pattern_matching_result.rs:119:5
++ --> $DIR/redundant_pattern_matching_result.rs:116:15
+ |
+LL | while let Err(_) = Ok::<i32, i32>(10) {}
+ | ----------^^^^^^--------------------- help: try this: `while Ok::<i32, i32>(10).is_err()`
+
+error: redundant pattern matching, consider using `is_ok()`
- --> $DIR/redundant_pattern_matching_result.rs:124:5
++ --> $DIR/redundant_pattern_matching_result.rs:118:5
+ |
+LL | / match Ok::<i32, i32>(42) {
+LL | | Ok(_) => true,
+LL | | Err(_) => false,
+LL | | };
+ | |_____^ help: try this: `Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
++ --> $DIR/redundant_pattern_matching_result.rs:123:5
+ |
+LL | / match Err::<i32, i32>(42) {
+LL | | Ok(_) => false,
+LL | | Err(_) => true,
+LL | | };
+ | |_____^ help: try this: `Err::<i32, i32>(42).is_err()`
+
+error: aborting due to 22 previous errors
+
--- /dev/null
-
+// run-rustfix
+#![warn(clippy::result_map_unit_fn)]
+#![allow(unused)]
++#![allow(clippy::uninlined_format_args)]
+
+fn do_nothing<T>(_: T) {}
+
+fn diverge<T>(_: T) -> ! {
+ panic!()
+}
+
+fn plus_one(value: usize) -> usize {
+ value + 1
+}
+
+struct HasResult {
+ field: Result<usize, usize>,
+}
+
+impl HasResult {
+ fn do_result_nothing(&self, value: usize) {}
+
+ fn do_result_plus_one(&self, value: usize) -> usize {
+ value + 1
+ }
+}
+
+#[rustfmt::skip]
+fn result_map_unit_fn() {
+ let x = HasResult { field: Ok(10) };
+
+ x.field.map(plus_one);
+ let _: Result<(), usize> = x.field.map(do_nothing);
+
+ if let Ok(x_field) = x.field { do_nothing(x_field) }
+
+ if let Ok(x_field) = x.field { do_nothing(x_field) }
+
+ if let Ok(x_field) = x.field { diverge(x_field) }
+
+ let captured = 10;
+ if let Ok(value) = x.field { do_nothing(value + captured) };
+ let _: Result<(), usize> = x.field.map(|value| do_nothing(value + captured));
+
+ if let Ok(value) = x.field { x.do_result_nothing(value + captured) }
+
+ if let Ok(value) = x.field { x.do_result_plus_one(value + captured); }
+
+
+ if let Ok(value) = x.field { do_nothing(value + captured) }
+
+ if let Ok(value) = x.field { do_nothing(value + captured) }
+
+ if let Ok(value) = x.field { do_nothing(value + captured); }
+
+ if let Ok(value) = x.field { do_nothing(value + captured); }
+
+
+ if let Ok(value) = x.field { diverge(value + captured) }
+
+ if let Ok(value) = x.field { diverge(value + captured) }
+
+ if let Ok(value) = x.field { diverge(value + captured); }
+
+ if let Ok(value) = x.field { diverge(value + captured); }
+
+
+ x.field.map(|value| plus_one(value + captured));
+ x.field.map(|value| { plus_one(value + captured) });
+ if let Ok(value) = x.field { let y = plus_one(value + captured); }
+
+ if let Ok(value) = x.field { plus_one(value + captured); }
+
+ if let Ok(value) = x.field { plus_one(value + captured); }
+
+
+ if let Ok(ref value) = x.field { do_nothing(value + captured) }
+
+ if let Ok(value) = x.field { println!("{:?}", value) }
+}
+
+fn main() {}
--- /dev/null
-
+// run-rustfix
+#![warn(clippy::result_map_unit_fn)]
+#![allow(unused)]
++#![allow(clippy::uninlined_format_args)]
+
+fn do_nothing<T>(_: T) {}
+
+fn diverge<T>(_: T) -> ! {
+ panic!()
+}
+
+fn plus_one(value: usize) -> usize {
+ value + 1
+}
+
+struct HasResult {
+ field: Result<usize, usize>,
+}
+
+impl HasResult {
+ fn do_result_nothing(&self, value: usize) {}
+
+ fn do_result_plus_one(&self, value: usize) -> usize {
+ value + 1
+ }
+}
+
+#[rustfmt::skip]
+fn result_map_unit_fn() {
+ let x = HasResult { field: Ok(10) };
+
+ x.field.map(plus_one);
+ let _: Result<(), usize> = x.field.map(do_nothing);
+
+ x.field.map(do_nothing);
+
+ x.field.map(do_nothing);
+
+ x.field.map(diverge);
+
+ let captured = 10;
+ if let Ok(value) = x.field { do_nothing(value + captured) };
+ let _: Result<(), usize> = x.field.map(|value| do_nothing(value + captured));
+
+ x.field.map(|value| x.do_result_nothing(value + captured));
+
+ x.field.map(|value| { x.do_result_plus_one(value + captured); });
+
+
+ x.field.map(|value| do_nothing(value + captured));
+
+ x.field.map(|value| { do_nothing(value + captured) });
+
+ x.field.map(|value| { do_nothing(value + captured); });
+
+ x.field.map(|value| { { do_nothing(value + captured); } });
+
+
+ x.field.map(|value| diverge(value + captured));
+
+ x.field.map(|value| { diverge(value + captured) });
+
+ x.field.map(|value| { diverge(value + captured); });
+
+ x.field.map(|value| { { diverge(value + captured); } });
+
+
+ x.field.map(|value| plus_one(value + captured));
+ x.field.map(|value| { plus_one(value + captured) });
+ x.field.map(|value| { let y = plus_one(value + captured); });
+
+ x.field.map(|value| { plus_one(value + captured); });
+
+ x.field.map(|value| { { plus_one(value + captured); } });
+
+
+ x.field.map(|ref value| { do_nothing(value + captured) });
+
+ x.field.map(|value| println!("{:?}", value));
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+#![warn(clippy::reversed_empty_ranges)]
++#![allow(clippy::uninlined_format_args)]
+
+const ANSWER: i32 = 42;
+
+fn main() {
+ // These should be linted:
+
+ (21..=42).rev().for_each(|x| println!("{}", x));
+ let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::<Vec<_>>();
+
+ for _ in (-42..=-21).rev() {}
+ for _ in (21u32..42u32).rev() {}
+
+ // These should be ignored as they are not empty ranges:
+
+ (21..=42).for_each(|x| println!("{}", x));
+ (21..42).for_each(|x| println!("{}", x));
+
+ let arr = [1, 2, 3, 4, 5];
+ let _ = &arr[1..=3];
+ let _ = &arr[1..3];
+
+ for _ in 21..=42 {}
+ for _ in 21..42 {}
+
+ // This range is empty but should be ignored, see issue #5689
+ let _ = &arr[0..0];
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::reversed_empty_ranges)]
++#![allow(clippy::uninlined_format_args)]
+
+const ANSWER: i32 = 42;
+
+fn main() {
+ // These should be linted:
+
+ (42..=21).for_each(|x| println!("{}", x));
+ let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::<Vec<_>>();
+
+ for _ in -21..=-42 {}
+ for _ in 42u32..21u32 {}
+
+ // These should be ignored as they are not empty ranges:
+
+ (21..=42).for_each(|x| println!("{}", x));
+ (21..42).for_each(|x| println!("{}", x));
+
+ let arr = [1, 2, 3, 4, 5];
+ let _ = &arr[1..=3];
+ let _ = &arr[1..3];
+
+ for _ in 21..=42 {}
+ for _ in 21..42 {}
+
+ // This range is empty but should be ignored, see issue #5689
+ let _ = &arr[0..0];
+}
--- /dev/null
- --> $DIR/reversed_empty_ranges_fixable.rs:9:5
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_fixable.rs:10:13
++ --> $DIR/reversed_empty_ranges_fixable.rs:10:5
+ |
+LL | (42..=21).for_each(|x| println!("{}", x));
+ | ^^^^^^^^^
+ |
+ = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings`
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | (21..=42).rev().for_each(|x| println!("{}", x));
+ | ~~~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_fixable.rs:12:14
++ --> $DIR/reversed_empty_ranges_fixable.rs:11:13
+ |
+LL | let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::<Vec<_>>();
+ | ^^^^^^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::<Vec<_>>();
+ | ~~~~~~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_fixable.rs:13:14
++ --> $DIR/reversed_empty_ranges_fixable.rs:13:14
+ |
+LL | for _ in -21..=-42 {}
+ | ^^^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for _ in (-42..=-21).rev() {}
+ | ~~~~~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
++ --> $DIR/reversed_empty_ranges_fixable.rs:14:14
+ |
+LL | for _ in 42u32..21u32 {}
+ | ^^^^^^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for _ in (21u32..42u32).rev() {}
+ | ~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 4 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::reversed_empty_ranges)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ const MAX_LEN: usize = 42;
+
+ for i in (0..10).rev() {
+ println!("{}", i);
+ }
+
+ for i in (0..=10).rev() {
+ println!("{}", i);
+ }
+
+ for i in (0..MAX_LEN).rev() {
+ println!("{}", i);
+ }
+
+ for i in 5..=5 {
+ // not an error, this is the range with only one element “5”
+ println!("{}", i);
+ }
+
+ for i in 0..10 {
+ // not an error, the start index is less than the end index
+ println!("{}", i);
+ }
+
+ for i in -10..0 {
+ // not an error
+ println!("{}", i);
+ }
+
+ for i in (0..10).rev().map(|x| x * 2) {
+ println!("{}", i);
+ }
+
+ // testing that the empty range lint folds constants
+ for i in (5 + 4..10).rev() {
+ println!("{}", i);
+ }
+
+ for i in ((3 - 1)..(5 + 2)).rev() {
+ println!("{}", i);
+ }
+
+ for i in (2 * 2)..(2 * 3) {
+ // no error, 4..6 is fine
+ println!("{}", i);
+ }
+
+ let x = 42;
+ for i in x..10 {
+ // no error, not constant-foldable
+ println!("{}", i);
+ }
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::reversed_empty_ranges)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ const MAX_LEN: usize = 42;
+
+ for i in 10..0 {
+ println!("{}", i);
+ }
+
+ for i in 10..=0 {
+ println!("{}", i);
+ }
+
+ for i in MAX_LEN..0 {
+ println!("{}", i);
+ }
+
+ for i in 5..=5 {
+ // not an error, this is the range with only one element “5”
+ println!("{}", i);
+ }
+
+ for i in 0..10 {
+ // not an error, the start index is less than the end index
+ println!("{}", i);
+ }
+
+ for i in -10..0 {
+ // not an error
+ println!("{}", i);
+ }
+
+ for i in (10..0).map(|x| x * 2) {
+ println!("{}", i);
+ }
+
+ // testing that the empty range lint folds constants
+ for i in 10..5 + 4 {
+ println!("{}", i);
+ }
+
+ for i in (5 + 2)..(3 - 1) {
+ println!("{}", i);
+ }
+
+ for i in (2 * 2)..(2 * 3) {
+ // no error, 4..6 is fine
+ println!("{}", i);
+ }
+
+ let x = 42;
+ for i in x..10 {
+ // no error, not constant-foldable
+ println!("{}", i);
+ }
+}
--- /dev/null
- --> $DIR/reversed_empty_ranges_loops_fixable.rs:7:14
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_loops_fixable.rs:11:14
++ --> $DIR/reversed_empty_ranges_loops_fixable.rs:8:14
+ |
+LL | for i in 10..0 {
+ | ^^^^^
+ |
+ = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings`
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for i in (0..10).rev() {
+ | ~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_loops_fixable.rs:15:14
++ --> $DIR/reversed_empty_ranges_loops_fixable.rs:12:14
+ |
+LL | for i in 10..=0 {
+ | ^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for i in (0..=10).rev() {
+ | ~~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_loops_fixable.rs:34:14
++ --> $DIR/reversed_empty_ranges_loops_fixable.rs:16:14
+ |
+LL | for i in MAX_LEN..0 {
+ | ^^^^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for i in (0..MAX_LEN).rev() {
+ | ~~~~~~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_loops_fixable.rs:39:14
++ --> $DIR/reversed_empty_ranges_loops_fixable.rs:35:14
+ |
+LL | for i in (10..0).map(|x| x * 2) {
+ | ^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for i in (0..10).rev().map(|x| x * 2) {
+ | ~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_loops_fixable.rs:43:14
++ --> $DIR/reversed_empty_ranges_loops_fixable.rs:40:14
+ |
+LL | for i in 10..5 + 4 {
+ | ^^^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for i in (5 + 4..10).rev() {
+ | ~~~~~~~~~~~~~~~~~
+
+error: this range is empty so it will yield no values
++ --> $DIR/reversed_empty_ranges_loops_fixable.rs:44:14
+ |
+LL | for i in (5 + 2)..(3 - 1) {
+ | ^^^^^^^^^^^^^^^^
+ |
+help: consider using the following if you are attempting to iterate over this range in reverse
+ |
+LL | for i in ((3 - 1)..(5 + 2)).rev() {
+ | ~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 6 previous errors
+
--- /dev/null
+#![warn(clippy::reversed_empty_ranges)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ for i in 5..5 {
+ println!("{}", i);
+ }
+
+ for i in (5 + 2)..(8 - 1) {
+ println!("{}", i);
+ }
+}
--- /dev/null
- --> $DIR/reversed_empty_ranges_loops_unfixable.rs:4:14
+error: this range is empty so it will yield no values
- --> $DIR/reversed_empty_ranges_loops_unfixable.rs:8:14
++ --> $DIR/reversed_empty_ranges_loops_unfixable.rs:5:14
+ |
+LL | for i in 5..5 {
+ | ^^^^
+ |
+ = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings`
+
+error: this range is empty so it will yield no values
++ --> $DIR/reversed_empty_ranges_loops_unfixable.rs:9:14
+ |
+LL | for i in (5 + 2)..(8 - 1) {
+ | ^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
--- /dev/null
- #![allow(incomplete_features)]
+#![feature(adt_const_params)]
- #![allow(clippy::ifs_same_cond)] // This warning is different from `ifs_same_cond`.
- #![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks
+#![warn(clippy::same_functions_in_if_condition)]
++// ifs_same_cond warning is different from `ifs_same_cond`.
++// clippy::if_same_then_else, clippy::comparison_chain -- all empty blocks
++#![allow(incomplete_features)]
++#![allow(
++ clippy::comparison_chain,
++ clippy::if_same_then_else,
++ clippy::ifs_same_cond,
++ clippy::uninlined_format_args
++)]
+
+fn function() -> bool {
+ true
+}
+
+fn fn_arg(_arg: u8) -> bool {
+ true
+}
+
+struct Struct;
+
+impl Struct {
+ fn method(&self) -> bool {
+ true
+ }
+ fn method_arg(&self, _arg: u8) -> bool {
+ true
+ }
+}
+
+fn ifs_same_cond_fn() {
+ let a = 0;
+ let obj = Struct;
+
+ if function() {
+ } else if function() {
+ //~ ERROR ifs same condition
+ }
+
+ if fn_arg(a) {
+ } else if fn_arg(a) {
+ //~ ERROR ifs same condition
+ }
+
+ if obj.method() {
+ } else if obj.method() {
+ //~ ERROR ifs same condition
+ }
+
+ if obj.method_arg(a) {
+ } else if obj.method_arg(a) {
+ //~ ERROR ifs same condition
+ }
+
+ let mut v = vec![1];
+ if v.pop().is_none() {
+ //~ ERROR ifs same condition
+ } else if v.pop().is_none() {
+ }
+
+ if v.len() == 42 {
+ //~ ERROR ifs same condition
+ } else if v.len() == 42 {
+ }
+
+ if v.len() == 1 {
+ // ok, different conditions
+ } else if v.len() == 2 {
+ }
+
+ if fn_arg(0) {
+ // ok, different arguments.
+ } else if fn_arg(1) {
+ }
+
+ if obj.method_arg(0) {
+ // ok, different arguments.
+ } else if obj.method_arg(1) {
+ }
+
+ if a == 1 {
+ // ok, warning is on `ifs_same_cond` behalf.
+ } else if a == 1 {
+ }
+}
+
+fn main() {
+ // macro as condition (see #6168)
+ let os = if cfg!(target_os = "macos") {
+ "macos"
+ } else if cfg!(target_os = "windows") {
+ "windows"
+ } else {
+ "linux"
+ };
+ println!("{}", os);
+
+ #[derive(PartialEq, Eq)]
+ enum E {
+ A,
+ B,
+ }
+ fn generic<const P: E>() -> bool {
+ match P {
+ E::A => true,
+ E::B => false,
+ }
+ }
+ if generic::<{ E::A }>() {
+ println!("A");
+ } else if generic::<{ E::B }>() {
+ println!("B");
+ }
+}
--- /dev/null
- --> $DIR/same_functions_in_if_condition.rs:31:15
+error: this `if` has the same function call as a previous `if`
- --> $DIR/same_functions_in_if_condition.rs:30:8
++ --> $DIR/same_functions_in_if_condition.rs:37:15
+ |
+LL | } else if function() {
+ | ^^^^^^^^^^
+ |
+note: same as this
- --> $DIR/same_functions_in_if_condition.rs:36:15
++ --> $DIR/same_functions_in_if_condition.rs:36:8
+ |
+LL | if function() {
+ | ^^^^^^^^^^
+ = note: `-D clippy::same-functions-in-if-condition` implied by `-D warnings`
+
+error: this `if` has the same function call as a previous `if`
- --> $DIR/same_functions_in_if_condition.rs:35:8
++ --> $DIR/same_functions_in_if_condition.rs:42:15
+ |
+LL | } else if fn_arg(a) {
+ | ^^^^^^^^^
+ |
+note: same as this
- --> $DIR/same_functions_in_if_condition.rs:41:15
++ --> $DIR/same_functions_in_if_condition.rs:41:8
+ |
+LL | if fn_arg(a) {
+ | ^^^^^^^^^
+
+error: this `if` has the same function call as a previous `if`
- --> $DIR/same_functions_in_if_condition.rs:40:8
++ --> $DIR/same_functions_in_if_condition.rs:47:15
+ |
+LL | } else if obj.method() {
+ | ^^^^^^^^^^^^
+ |
+note: same as this
- --> $DIR/same_functions_in_if_condition.rs:46:15
++ --> $DIR/same_functions_in_if_condition.rs:46:8
+ |
+LL | if obj.method() {
+ | ^^^^^^^^^^^^
+
+error: this `if` has the same function call as a previous `if`
- --> $DIR/same_functions_in_if_condition.rs:45:8
++ --> $DIR/same_functions_in_if_condition.rs:52:15
+ |
+LL | } else if obj.method_arg(a) {
+ | ^^^^^^^^^^^^^^^^^
+ |
+note: same as this
- --> $DIR/same_functions_in_if_condition.rs:53:15
++ --> $DIR/same_functions_in_if_condition.rs:51:8
+ |
+LL | if obj.method_arg(a) {
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `if` has the same function call as a previous `if`
- --> $DIR/same_functions_in_if_condition.rs:51:8
++ --> $DIR/same_functions_in_if_condition.rs:59:15
+ |
+LL | } else if v.pop().is_none() {
+ | ^^^^^^^^^^^^^^^^^
+ |
+note: same as this
- --> $DIR/same_functions_in_if_condition.rs:58:15
++ --> $DIR/same_functions_in_if_condition.rs:57:8
+ |
+LL | if v.pop().is_none() {
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `if` has the same function call as a previous `if`
- --> $DIR/same_functions_in_if_condition.rs:56:8
++ --> $DIR/same_functions_in_if_condition.rs:64:15
+ |
+LL | } else if v.len() == 42 {
+ | ^^^^^^^^^^^^^
+ |
+note: same as this
++ --> $DIR/same_functions_in_if_condition.rs:62:8
+ |
+LL | if v.len() == 42 {
+ | ^^^^^^^^^^^^^
+
+error: aborting due to 6 previous errors
+
--- /dev/null
- #![allow(clippy::redundant_closure)]
+#![warn(clippy::semicolon_if_nothing_returned)]
++#![allow(clippy::redundant_closure, clippy::uninlined_format_args)]
+
+fn get_unit() {}
+
+// the functions below trigger the lint
+fn main() {
+ println!("Hello")
+}
+
+fn hello() {
+ get_unit()
+}
+
+fn basic101(x: i32) {
+ let y: i32;
+ y = x + 1
+}
+
+#[rustfmt::skip]
+fn closure_error() {
+ let _d = || {
+ hello()
+ };
+}
+
+#[rustfmt::skip]
+fn unsafe_checks_error() {
+ use std::mem::MaybeUninit;
+ use std::ptr;
+
+ let mut s = MaybeUninit::<String>::uninit();
+ let _d = || unsafe {
+ ptr::drop_in_place(s.as_mut_ptr())
+ };
+}
+
+// this is fine
+fn print_sum(a: i32, b: i32) {
+ println!("{}", a + b);
+ assert_eq!(true, false);
+}
+
+fn foo(x: i32) {
+ let y: i32;
+ if x < 1 {
+ y = 4;
+ } else {
+ y = 5;
+ }
+}
+
+fn bar(x: i32) {
+ let y: i32;
+ match x {
+ 1 => y = 4,
+ _ => y = 32,
+ }
+}
+
+fn foobar(x: i32) {
+ let y: i32;
+ 'label: {
+ y = x + 1;
+ }
+}
+
+fn loop_test(x: i32) {
+ let y: i32;
+ for &ext in &["stdout", "stderr", "fixed"] {
+ println!("{}", ext);
+ }
+}
+
+fn closure() {
+ let _d = || hello();
+}
+
+#[rustfmt::skip]
+fn closure_block() {
+ let _d = || { hello() };
+}
+
+unsafe fn some_unsafe_op() {}
+unsafe fn some_other_unsafe_fn() {}
+
+fn do_something() {
+ unsafe { some_unsafe_op() };
+
+ unsafe { some_other_unsafe_fn() };
+}
+
+fn unsafe_checks() {
+ use std::mem::MaybeUninit;
+ use std::ptr;
+
+ let mut s = MaybeUninit::<String>::uninit();
+ let _d = || unsafe { ptr::drop_in_place(s.as_mut_ptr()) };
+}
+
+// Issue #7768
+#[rustfmt::skip]
+fn macro_with_semicolon() {
+ macro_rules! repro {
+ () => {
+ while false {
+ }
+ };
+ }
+ repro!();
+}
+
+fn function_returning_option() -> Option<i32> {
+ Some(1)
+}
+
+// No warning
+fn let_else_stmts() {
+ let Some(x) = function_returning_option() else { return; };
+}
--- /dev/null
- error: aborting due to 14 previous errors
+error: method `add` can be confused for the standard trait method `std::ops::Add::add`
+ --> $DIR/method_list_1.rs:25:5
+ |
+LL | / pub fn add(self, other: T) -> T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Add` or choosing a less ambiguous method name
+ = note: `-D clippy::should-implement-trait` implied by `-D warnings`
+
+error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut`
+ --> $DIR/method_list_1.rs:29:5
+ |
+LL | / pub fn as_mut(&mut self) -> &mut T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name
+
+error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref`
+ --> $DIR/method_list_1.rs:33:5
+ |
+LL | / pub fn as_ref(&self) -> &T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name
+
+error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand`
+ --> $DIR/method_list_1.rs:37:5
+ |
+LL | / pub fn bitand(self, rhs: T) -> T {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name
+
+error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor`
+ --> $DIR/method_list_1.rs:41:5
+ |
+LL | / pub fn bitor(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name
+
+error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor`
+ --> $DIR/method_list_1.rs:45:5
+ |
+LL | / pub fn bitxor(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name
+
+error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow`
+ --> $DIR/method_list_1.rs:49:5
+ |
+LL | / pub fn borrow(&self) -> &str {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name
+
+error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut`
+ --> $DIR/method_list_1.rs:53:5
+ |
+LL | / pub fn borrow_mut(&mut self) -> &mut str {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name
+
+error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone`
+ --> $DIR/method_list_1.rs:57:5
+ |
+LL | / pub fn clone(&self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name
+
+error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp`
+ --> $DIR/method_list_1.rs:61:5
+ |
+LL | / pub fn cmp(&self, other: &Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name
+
++error: method `default` can be confused for the standard trait method `std::default::Default::default`
++ --> $DIR/method_list_1.rs:65:5
++ |
++LL | / pub fn default() -> Self {
++LL | | unimplemented!()
++LL | | }
++ | |_____^
++ |
++ = help: consider implementing the trait `std::default::Default` or choosing a less ambiguous method name
++
+error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref`
+ --> $DIR/method_list_1.rs:69:5
+ |
+LL | / pub fn deref(&self) -> &Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name
+
+error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut`
+ --> $DIR/method_list_1.rs:73:5
+ |
+LL | / pub fn deref_mut(&mut self) -> &mut Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name
+
+error: method `div` can be confused for the standard trait method `std::ops::Div::div`
+ --> $DIR/method_list_1.rs:77:5
+ |
+LL | / pub fn div(self, rhs: Self) -> Self {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name
+
+error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop`
+ --> $DIR/method_list_1.rs:81:5
+ |
+LL | / pub fn drop(&mut self) {
+LL | | unimplemented!()
+LL | | }
+ | |_____^
+ |
+ = help: consider implementing the trait `std::ops::Drop` or choosing a less ambiguous method name
+
++error: aborting due to 15 previous errors
+
--- /dev/null
-
+// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934
+// // run-rustfix
- #![allow(clippy::single_match)]
- #![allow(clippy::match_single_binding)]
- #![allow(unused_assignments)]
- #![allow(dead_code)]
+#![warn(clippy::significant_drop_in_scrutinee)]
++#![allow(dead_code, unused_assignments)]
++#![allow(clippy::match_single_binding, clippy::single_match, clippy::uninlined_format_args)]
+
+use std::num::ParseIntError;
+use std::ops::Deref;
+use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::RwLock;
+use std::sync::{Mutex, MutexGuard};
+
+struct State {}
+
+impl State {
+ fn foo(&self) -> bool {
+ true
+ }
+
+ fn bar(&self) {}
+}
+
+fn should_not_trigger_lint_with_mutex_guard_outside_match() {
+ let mutex = Mutex::new(State {});
+
+ // Should not trigger lint because the temporary should drop at the `;` on line before the match
+ let is_foo = mutex.lock().unwrap().foo();
+ match is_foo {
+ true => {
+ mutex.lock().unwrap().bar();
+ },
+ false => {},
+ };
+}
+
+fn should_not_trigger_lint_with_mutex_guard_when_taking_ownership_in_match() {
+ let mutex = Mutex::new(State {});
+
+ // Should not trigger lint because the scrutinee is explicitly returning the MutexGuard,
+ // so its lifetime should not be surprising.
+ match mutex.lock() {
+ Ok(guard) => {
+ guard.foo();
+ mutex.lock().unwrap().bar();
+ },
+ _ => {},
+ };
+}
+
+fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() {
+ let mutex = Mutex::new(State {});
+
+ // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it
+ // is preserved until the end of the match, but there is no clear indication that this is the
+ // case.
+ match mutex.lock().unwrap().foo() {
+ true => {
+ mutex.lock().unwrap().bar();
+ },
+ false => {},
+ };
+}
+
+fn should_not_trigger_lint_with_mutex_guard_in_match_scrutinee_when_lint_allowed() {
+ let mutex = Mutex::new(State {});
+
+ // Lint should not be triggered because it is "allowed" below.
+ #[allow(clippy::significant_drop_in_scrutinee)]
+ match mutex.lock().unwrap().foo() {
+ true => {
+ mutex.lock().unwrap().bar();
+ },
+ false => {},
+ };
+}
+
+fn should_not_trigger_lint_for_insignificant_drop() {
+ // Should not trigger lint because there are no temporaries whose drops have a significant
+ // side effect.
+ match 1u64.to_string().is_empty() {
+ true => {
+ println!("It was empty")
+ },
+ false => {
+ println!("It was not empty")
+ },
+ }
+}
+
+struct StateWithMutex {
+ m: Mutex<u64>,
+}
+
+struct MutexGuardWrapper<'a> {
+ mg: MutexGuard<'a, u64>,
+}
+
+impl<'a> MutexGuardWrapper<'a> {
+ fn get_the_value(&self) -> u64 {
+ *self.mg.deref()
+ }
+}
+
+struct MutexGuardWrapperWrapper<'a> {
+ mg: MutexGuardWrapper<'a>,
+}
+
+impl<'a> MutexGuardWrapperWrapper<'a> {
+ fn get_the_value(&self) -> u64 {
+ *self.mg.mg.deref()
+ }
+}
+
+impl StateWithMutex {
+ fn lock_m(&self) -> MutexGuardWrapper<'_> {
+ MutexGuardWrapper {
+ mg: self.m.lock().unwrap(),
+ }
+ }
+
+ fn lock_m_m(&self) -> MutexGuardWrapperWrapper<'_> {
+ MutexGuardWrapperWrapper {
+ mg: MutexGuardWrapper {
+ mg: self.m.lock().unwrap(),
+ },
+ }
+ }
+
+ fn foo(&self) -> bool {
+ true
+ }
+
+ fn bar(&self) {}
+}
+
+fn should_trigger_lint_with_wrapped_mutex() {
+ let s = StateWithMutex { m: Mutex::new(1) };
+
+ // Should trigger lint because a temporary contains a type with a significant drop and its
+ // lifetime is not obvious. Additionally, it is not obvious from looking at the scrutinee that
+ // the temporary contains such a type, making it potentially even more surprising.
+ match s.lock_m().get_the_value() {
+ 1 => {
+ println!("Got 1. Is it still 1?");
+ println!("{}", s.lock_m().get_the_value());
+ },
+ 2 => {
+ println!("Got 2. Is it still 2?");
+ println!("{}", s.lock_m().get_the_value());
+ },
+ _ => {},
+ }
+ println!("All done!");
+}
+
+fn should_trigger_lint_with_double_wrapped_mutex() {
+ let s = StateWithMutex { m: Mutex::new(1) };
+
+ // Should trigger lint because a temporary contains a type which further contains a type with a
+ // significant drop and its lifetime is not obvious. Additionally, it is not obvious from
+ // looking at the scrutinee that the temporary contains such a type, making it potentially even
+ // more surprising.
+ match s.lock_m_m().get_the_value() {
+ 1 => {
+ println!("Got 1. Is it still 1?");
+ println!("{}", s.lock_m().get_the_value());
+ },
+ 2 => {
+ println!("Got 2. Is it still 2?");
+ println!("{}", s.lock_m().get_the_value());
+ },
+ _ => {},
+ }
+ println!("All done!");
+}
+
+struct Counter {
+ i: AtomicU64,
+}
+
+#[clippy::has_significant_drop]
+struct CounterWrapper<'a> {
+ counter: &'a Counter,
+}
+
+impl<'a> CounterWrapper<'a> {
+ fn new(counter: &Counter) -> CounterWrapper {
+ counter.i.fetch_add(1, Ordering::Relaxed);
+ CounterWrapper { counter }
+ }
+}
+
+impl<'a> Drop for CounterWrapper<'a> {
+ fn drop(&mut self) {
+ self.counter.i.fetch_sub(1, Ordering::Relaxed);
+ }
+}
+
+impl Counter {
+ fn temp_increment(&self) -> Vec<CounterWrapper> {
+ vec![CounterWrapper::new(self), CounterWrapper::new(self)]
+ }
+}
+
+fn should_trigger_lint_for_vec() {
+ let counter = Counter { i: AtomicU64::new(0) };
+
+ // Should trigger lint because the temporary in the scrutinee returns a collection of types
+ // which have significant drops. The types with significant drops are also non-obvious when
+ // reading the expression in the scrutinee.
+ match counter.temp_increment().len() {
+ 2 => {
+ let current_count = counter.i.load(Ordering::Relaxed);
+ println!("Current count {}", current_count);
+ assert_eq!(current_count, 0);
+ },
+ 1 => {},
+ 3 => {},
+ _ => {},
+ };
+}
+
+struct StateWithField {
+ s: String,
+}
+
+// Should trigger lint only on the type in the tuple which is created using a temporary
+// with a significant drop. Additionally, this test ensures that the format of the tuple
+// is preserved correctly in the suggestion.
+fn should_trigger_lint_for_tuple_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+
+ {
+ match (mutex1.lock().unwrap().s.len(), true) {
+ (3, _) => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ println!("done");
+ },
+ (_, _) => {},
+ };
+
+ match (true, mutex1.lock().unwrap().s.len(), true) {
+ (_, 3, _) => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ println!("done");
+ },
+ (_, _, _) => {},
+ };
+
+ let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() });
+ match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) {
+ (3, _, 3) => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ mutex2.lock().unwrap().s.len();
+ println!("done");
+ },
+ (_, _, _) => {},
+ };
+
+ let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() });
+ match mutex3.lock().unwrap().s.as_str() {
+ "three" => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ mutex2.lock().unwrap().s.len();
+ println!("done");
+ },
+ _ => {},
+ };
+
+ match (true, mutex3.lock().unwrap().s.as_str()) {
+ (_, "three") => {
+ println!("started");
+ mutex1.lock().unwrap().s.len();
+ mutex2.lock().unwrap().s.len();
+ println!("done");
+ },
+ (_, _) => {},
+ };
+ }
+}
+
+// Should trigger lint when either side of a binary operation creates a temporary with a
+// significant drop.
+// To avoid potential unnecessary copies or creating references that would trigger the significant
+// drop problem, the lint recommends moving the entire binary operation.
+fn should_trigger_lint_for_accessing_field_in_mutex_in_one_side_of_binary_op() {
+ let mutex = Mutex::new(StateWithField { s: "state".to_owned() });
+
+ match mutex.lock().unwrap().s.len() > 1 {
+ true => {
+ mutex.lock().unwrap().s.len();
+ },
+ false => {},
+ };
+
+ match 1 < mutex.lock().unwrap().s.len() {
+ true => {
+ mutex.lock().unwrap().s.len();
+ },
+ false => {},
+ };
+}
+
+// Should trigger lint when both sides of a binary operation creates a temporary with a
+// significant drop.
+// To avoid potential unnecessary copies or creating references that would trigger the significant
+// drop problem, the lint recommends moving the entire binary operation.
+fn should_trigger_lint_for_accessing_fields_in_mutex_in_both_sides_of_binary_op() {
+ let mutex1 = Mutex::new(StateWithField { s: "state".to_owned() });
+ let mutex2 = Mutex::new(StateWithField {
+ s: "statewithfield".to_owned(),
+ });
+
+ match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() {
+ true => {
+ println!(
+ "{} < {}",
+ mutex1.lock().unwrap().s.len(),
+ mutex2.lock().unwrap().s.len()
+ );
+ },
+ false => {},
+ };
+
+ match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() {
+ true => {
+ println!(
+ "{} >= {}",
+ mutex1.lock().unwrap().s.len(),
+ mutex2.lock().unwrap().s.len()
+ );
+ },
+ false => {},
+ };
+}
+
+fn should_not_trigger_lint_for_closure_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+
+ let get_mutex_guard = || mutex1.lock().unwrap().s.len();
+
+ // Should not trigger lint because the temporary with a significant drop will be dropped
+ // at the end of the closure, so the MutexGuard will be unlocked and not have a potentially
+ // surprising lifetime.
+ match get_mutex_guard() > 1 {
+ true => {
+ mutex1.lock().unwrap().s.len();
+ },
+ false => {},
+ };
+}
+
+fn should_trigger_lint_for_return_from_closure_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+
+ let get_mutex_guard = || mutex1.lock().unwrap();
+
+ // Should trigger lint because the temporary with a significant drop is returned from the
+ // closure but not used directly in any match arms, so it has a potentially surprising lifetime.
+ match get_mutex_guard().s.len() > 1 {
+ true => {
+ mutex1.lock().unwrap().s.len();
+ },
+ false => {},
+ };
+}
+
+fn should_trigger_lint_for_return_from_match_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+ let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() });
+
+ let i = 100;
+
+ // Should trigger lint because the nested match within the scrutinee returns a temporary with a
+ // significant drop is but not used directly in any match arms, so it has a potentially
+ // surprising lifetime.
+ match match i {
+ 100 => mutex1.lock().unwrap(),
+ _ => mutex2.lock().unwrap(),
+ }
+ .s
+ .len()
+ > 1
+ {
+ true => {
+ mutex1.lock().unwrap().s.len();
+ },
+ false => {
+ println!("nothing to do here");
+ },
+ };
+}
+
+fn should_trigger_lint_for_return_from_if_in_scrutinee() {
+ let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() });
+ let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() });
+
+ let i = 100;
+
+ // Should trigger lint because the nested if-expression within the scrutinee returns a temporary
+ // with a significant drop is but not used directly in any match arms, so it has a potentially
+ // surprising lifetime.
+ match if i > 1 {
+ mutex1.lock().unwrap()
+ } else {
+ mutex2.lock().unwrap()
+ }
+ .s
+ .len()
+ > 1
+ {
+ true => {
+ mutex1.lock().unwrap().s.len();
+ },
+ false => {},
+ };
+}
+
+fn should_not_trigger_lint_for_if_in_scrutinee() {
+ let mutex = Mutex::new(StateWithField { s: "state".to_owned() });
+
+ let i = 100;
+
+ // Should not trigger the lint because the temporary with a significant drop *is* dropped within
+ // the body of the if-expression nested within the match scrutinee, and therefore does not have
+ // a potentially surprising lifetime.
+ match if i > 1 {
+ mutex.lock().unwrap().s.len() > 1
+ } else {
+ false
+ } {
+ true => {
+ mutex.lock().unwrap().s.len();
+ },
+ false => {},
+ };
+}
+
+struct StateWithBoxedMutexGuard {
+ u: Mutex<u64>,
+}
+
+impl StateWithBoxedMutexGuard {
+ fn new() -> StateWithBoxedMutexGuard {
+ StateWithBoxedMutexGuard { u: Mutex::new(42) }
+ }
+ fn lock(&self) -> Box<MutexGuard<u64>> {
+ Box::new(self.u.lock().unwrap())
+ }
+}
+
+fn should_trigger_lint_for_boxed_mutex_guard() {
+ let s = StateWithBoxedMutexGuard::new();
+
+ // Should trigger lint because a temporary Box holding a type with a significant drop in a match
+ // scrutinee may have a potentially surprising lifetime.
+ match s.lock().deref().deref() {
+ 0 | 1 => println!("Value was less than 2"),
+ _ => println!("Value is {}", s.lock().deref()),
+ };
+}
+
+struct StateStringWithBoxedMutexGuard {
+ s: Mutex<String>,
+}
+
+impl StateStringWithBoxedMutexGuard {
+ fn new() -> StateStringWithBoxedMutexGuard {
+ StateStringWithBoxedMutexGuard {
+ s: Mutex::new("A String".to_owned()),
+ }
+ }
+ fn lock(&self) -> Box<MutexGuard<String>> {
+ Box::new(self.s.lock().unwrap())
+ }
+}
+
+fn should_trigger_lint_for_boxed_mutex_guard_holding_string() {
+ let s = StateStringWithBoxedMutexGuard::new();
+
+ let matcher = String::from("A String");
+
+ // Should trigger lint because a temporary Box holding a type with a significant drop in a match
+ // scrutinee may have a potentially surprising lifetime.
+ match s.lock().deref().deref() {
+ matcher => println!("Value is {}", s.lock().deref()),
+ _ => println!("Value was not a match"),
+ };
+}
+
+struct StateWithIntField {
+ i: u64,
+}
+
+// Should trigger lint when either side of an assign expression contains a temporary with a
+// significant drop, because the temporary's lifetime will be extended to the end of the match.
+// To avoid potential unnecessary copies or creating references that would trigger the significant
+// drop problem, the lint recommends moving the entire binary operation.
+fn should_trigger_lint_in_assign_expr() {
+ let mutex = Mutex::new(StateWithIntField { i: 10 });
+
+ let mut i = 100;
+
+ match mutex.lock().unwrap().i = i {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
+ },
+ };
+
+ match i = mutex.lock().unwrap().i {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
+ },
+ };
+
+ match mutex.lock().unwrap().i += 1 {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
+ },
+ };
+
+ match i += mutex.lock().unwrap().i {
+ _ => {
+ println!("{}", mutex.lock().unwrap().i);
+ },
+ };
+}
+
+#[derive(Debug)]
+enum RecursiveEnum {
+ Foo(Option<Box<RecursiveEnum>>),
+}
+
+#[derive(Debug)]
+enum GenericRecursiveEnum<T> {
+ Foo(T, Option<Box<GenericRecursiveEnum<T>>>),
+}
+
+fn should_not_cause_stack_overflow() {
+ // Test that when a type recursively contains itself, a stack overflow does not occur when
+ // checking sub-types for significant drops.
+ let f = RecursiveEnum::Foo(Some(Box::new(RecursiveEnum::Foo(None))));
+ match f {
+ RecursiveEnum::Foo(Some(f)) => {
+ println!("{:?}", f)
+ },
+ RecursiveEnum::Foo(f) => {
+ println!("{:?}", f)
+ },
+ }
+
+ let f = GenericRecursiveEnum::Foo(1u64, Some(Box::new(GenericRecursiveEnum::Foo(2u64, None))));
+ match f {
+ GenericRecursiveEnum::Foo(i, Some(f)) => {
+ println!("{} {:?}", i, f)
+ },
+ GenericRecursiveEnum::Foo(i, f) => {
+ println!("{} {:?}", i, f)
+ },
+ }
+}
+
+fn should_not_produce_lint_for_try_desugar() -> Result<u64, ParseIntError> {
+ // TryDesugar (i.e. using `?` for a Result type) will turn into a match but is out of scope
+ // for this lint
+ let rwlock = RwLock::new("1".to_string());
+ let result = rwlock.read().unwrap().parse::<u64>()?;
+ println!("{}", result);
+ rwlock.write().unwrap().push('2');
+ Ok(result)
+}
+
+struct ResultReturner {
+ s: String,
+}
+
+impl ResultReturner {
+ fn to_number(&self) -> Result<i64, ParseIntError> {
+ self.s.parse::<i64>()
+ }
+}
+
+fn should_trigger_lint_for_non_ref_move_and_clone_suggestion() {
+ let rwlock = RwLock::<ResultReturner>::new(ResultReturner { s: "1".to_string() });
+ match rwlock.read().unwrap().to_number() {
+ Ok(n) => println!("Converted to number: {}", n),
+ Err(e) => println!("Could not convert {} to number", e),
+ };
+}
+
+fn should_trigger_lint_for_read_write_lock_for_loop() {
+ // For-in loops desugar to match expressions and are prone to the type of deadlock this lint is
+ // designed to look for.
+ let rwlock = RwLock::<Vec<String>>::new(vec!["1".to_string()]);
+ for s in rwlock.read().unwrap().iter() {
+ println!("{}", s);
+ }
+}
+
+fn do_bar(mutex: &Mutex<State>) {
+ mutex.lock().unwrap().bar();
+}
+
+fn should_trigger_lint_without_significant_drop_in_arm() {
+ let mutex = Mutex::new(State {});
+
+ // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it
+ // is preserved until the end of the match, but there is no clear indication that this is the
+ // case.
+ match mutex.lock().unwrap().foo() {
+ true => do_bar(&mutex),
+ false => {},
+ };
+}
+
+fn should_not_trigger_on_significant_iterator_drop() {
+ let lines = std::io::stdin().lines();
+ for line in lines {
+ println!("foo: {}", line.unwrap());
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/significant_drop_in_scrutinee.rs:59:11
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:145:11
++ --> $DIR/significant_drop_in_scrutinee.rs:56:11
+ |
+LL | match mutex.lock().unwrap().foo() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | true => {
+LL | mutex.lock().unwrap().bar();
+ | --------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+ = note: `-D clippy::significant-drop-in-scrutinee` implied by `-D warnings`
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex.lock().unwrap().foo();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:166:11
++ --> $DIR/significant_drop_in_scrutinee.rs:142:11
+ |
+LL | match s.lock_m().get_the_value() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | println!("{}", s.lock_m().get_the_value());
+ | ---------- another value with significant `Drop` created here
+...
+LL | }
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = s.lock_m().get_the_value();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:214:11
++ --> $DIR/significant_drop_in_scrutinee.rs:163:11
+ |
+LL | match s.lock_m_m().get_the_value() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | println!("{}", s.lock_m().get_the_value());
+ | ---------- another value with significant `Drop` created here
+...
+LL | }
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = s.lock_m_m().get_the_value();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:237:16
++ --> $DIR/significant_drop_in_scrutinee.rs:211:11
+ |
+LL | match counter.temp_increment().len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = counter.temp_increment().len();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:246:22
++ --> $DIR/significant_drop_in_scrutinee.rs:234:16
+ |
+LL | match (mutex1.lock().unwrap().s.len(), true) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len();
+LL ~ match (value, true) {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:256:16
++ --> $DIR/significant_drop_in_scrutinee.rs:243:22
+ |
+LL | match (true, mutex1.lock().unwrap().s.len(), true) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len();
+LL ~ match (true, value, true) {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:256:54
++ --> $DIR/significant_drop_in_scrutinee.rs:253:16
+ |
+LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+LL | mutex2.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len();
+LL ~ match (value, true, mutex2.lock().unwrap().s.len()) {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:267:15
++ --> $DIR/significant_drop_in_scrutinee.rs:253:54
+ |
+LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+LL | mutex2.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex2.lock().unwrap().s.len();
+LL ~ match (mutex1.lock().unwrap().s.len(), true, value) {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:277:22
++ --> $DIR/significant_drop_in_scrutinee.rs:264:15
+ |
+LL | match mutex3.lock().unwrap().s.as_str() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+LL | mutex2.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:296:11
++ --> $DIR/significant_drop_in_scrutinee.rs:274:22
+ |
+LL | match (true, mutex3.lock().unwrap().s.as_str()) {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+LL | mutex2.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:303:11
++ --> $DIR/significant_drop_in_scrutinee.rs:293:11
+ |
+LL | match mutex.lock().unwrap().s.len() > 1 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | true => {
+LL | mutex.lock().unwrap().s.len();
+ | --------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex.lock().unwrap().s.len() > 1;
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:321:11
++ --> $DIR/significant_drop_in_scrutinee.rs:300:11
+ |
+LL | match 1 < mutex.lock().unwrap().s.len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | true => {
+LL | mutex.lock().unwrap().s.len();
+ | --------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = 1 < mutex.lock().unwrap().s.len();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:332:11
++ --> $DIR/significant_drop_in_scrutinee.rs:318:11
+ |
+LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len(),
+ | ---------------------- another value with significant `Drop` created here
+LL | mutex2.lock().unwrap().s.len()
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:367:11
++ --> $DIR/significant_drop_in_scrutinee.rs:329:11
+ |
+LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | mutex1.lock().unwrap().s.len(),
+ | ---------------------- another value with significant `Drop` created here
+LL | mutex2.lock().unwrap().s.len()
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:384:11
++ --> $DIR/significant_drop_in_scrutinee.rs:364:11
+ |
+LL | match get_mutex_guard().s.len() > 1 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | true => {
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = get_mutex_guard().s.len() > 1;
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:410:11
++ --> $DIR/significant_drop_in_scrutinee.rs:381:11
+ |
+LL | match match i {
+ | ___________^
+LL | | 100 => mutex1.lock().unwrap(),
+LL | | _ => mutex2.lock().unwrap(),
+LL | | }
+LL | | .s
+LL | | .len()
+LL | | > 1
+ | |___________^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = match i {
+LL + 100 => mutex1.lock().unwrap(),
+LL + _ => mutex2.lock().unwrap(),
+LL + }
+LL + .s
+LL + .len()
+LL + > 1;
+LL ~ match value
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:464:11
++ --> $DIR/significant_drop_in_scrutinee.rs:407:11
+ |
+LL | match if i > 1 {
+ | ___________^
+LL | | mutex1.lock().unwrap()
+LL | | } else {
+LL | | mutex2.lock().unwrap()
+... |
+LL | | .len()
+LL | | > 1
+ | |___________^
+...
+LL | mutex1.lock().unwrap().s.len();
+ | ---------------------- another value with significant `Drop` created here
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = if i > 1 {
+LL + mutex1.lock().unwrap()
+LL + } else {
+LL + mutex2.lock().unwrap()
+LL + }
+LL + .s
+LL + .len()
+LL + > 1;
+LL ~ match value
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:492:11
++ --> $DIR/significant_drop_in_scrutinee.rs:461:11
+ |
+LL | match s.lock().deref().deref() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+LL | 0 | 1 => println!("Value was less than 2"),
+LL | _ => println!("Value is {}", s.lock().deref()),
+ | ---------------- another value with significant `Drop` created here
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match and create a copy
+ |
+LL ~ let value = *s.lock().deref().deref();
+LL ~ match value {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:511:11
++ --> $DIR/significant_drop_in_scrutinee.rs:489:11
+ |
+LL | match s.lock().deref().deref() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+LL | matcher => println!("Value is {}", s.lock().deref()),
+ | ---------------- another value with significant `Drop` created here
+LL | _ => println!("Value was not a match"),
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:517:11
++ --> $DIR/significant_drop_in_scrutinee.rs:508:11
+ |
+LL | match mutex.lock().unwrap().i = i {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | _ => {
+LL | println!("{}", mutex.lock().unwrap().i);
+ | --------------------- another value with significant `Drop` created here
+LL | },
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ mutex.lock().unwrap().i = i;
+LL ~ match () {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:523:11
++ --> $DIR/significant_drop_in_scrutinee.rs:514:11
+ |
+LL | match i = mutex.lock().unwrap().i {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | _ => {
+LL | println!("{}", mutex.lock().unwrap().i);
+ | --------------------- another value with significant `Drop` created here
+LL | },
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ i = mutex.lock().unwrap().i;
+LL ~ match () {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:529:11
++ --> $DIR/significant_drop_in_scrutinee.rs:520:11
+ |
+LL | match mutex.lock().unwrap().i += 1 {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | _ => {
+LL | println!("{}", mutex.lock().unwrap().i);
+ | --------------------- another value with significant `Drop` created here
+LL | },
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ mutex.lock().unwrap().i += 1;
+LL ~ match () {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:592:11
++ --> $DIR/significant_drop_in_scrutinee.rs:526:11
+ |
+LL | match i += mutex.lock().unwrap().i {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | _ => {
+LL | println!("{}", mutex.lock().unwrap().i);
+ | --------------------- another value with significant `Drop` created here
+LL | },
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ i += mutex.lock().unwrap().i;
+LL ~ match () {
+ |
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
- --> $DIR/significant_drop_in_scrutinee.rs:602:14
++ --> $DIR/significant_drop_in_scrutinee.rs:589:11
+ |
+LL | match rwlock.read().unwrap().to_number() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+
+error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression
- --> $DIR/significant_drop_in_scrutinee.rs:617:11
++ --> $DIR/significant_drop_in_scrutinee.rs:599:14
+ |
+LL | for s in rwlock.read().unwrap().iter() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | println!("{}", s);
+LL | }
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+
+error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
++ --> $DIR/significant_drop_in_scrutinee.rs:614:11
+ |
+LL | match mutex.lock().unwrap().foo() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | };
+ | - temporary lives until here
+ |
+ = note: this might lead to deadlocks or other unexpected behavior
+help: try moving the temporary above the match
+ |
+LL ~ let value = mutex.lock().unwrap().foo();
+LL ~ match value {
+ |
+
+error: aborting due to 26 previous errors
+
--- /dev/null
+#![warn(clippy::single_match)]
++#![allow(clippy::uninlined_format_args)]
+
+fn dummy() {}
+
+fn single_match() {
+ let x = Some(1u8);
+
+ match x {
+ Some(y) => {
+ println!("{:?}", y);
+ },
+ _ => (),
+ };
+
+ let x = Some(1u8);
+ match x {
+ // Note the missing block braces.
+ // We suggest `if let Some(y) = x { .. }` because the macro
+ // is expanded before we can do anything.
+ Some(y) => println!("{:?}", y),
+ _ => (),
+ }
+
+ let z = (1u8, 1u8);
+ match z {
+ (2..=3, 7..=9) => dummy(),
+ _ => {},
+ };
+
+ // Not linted (pattern guards used)
+ match x {
+ Some(y) if y == 0 => println!("{:?}", y),
+ _ => (),
+ }
+
+ // Not linted (no block with statements in the single arm)
+ match z {
+ (2..=3, 7..=9) => println!("{:?}", z),
+ _ => println!("nope"),
+ }
+}
+
+enum Foo {
+ Bar,
+ Baz(u8),
+}
+use std::borrow::Cow;
+use Foo::*;
+
+fn single_match_know_enum() {
+ let x = Some(1u8);
+ let y: Result<_, i8> = Ok(1i8);
+
+ match x {
+ Some(y) => dummy(),
+ None => (),
+ };
+
+ match y {
+ Ok(y) => dummy(),
+ Err(..) => (),
+ };
+
+ let c = Cow::Borrowed("");
+
+ match c {
+ Cow::Borrowed(..) => dummy(),
+ Cow::Owned(..) => (),
+ };
+
+ let z = Foo::Bar;
+ // no warning
+ match z {
+ Bar => println!("42"),
+ Baz(_) => (),
+ }
+
+ match z {
+ Baz(_) => println!("42"),
+ Bar => (),
+ }
+}
+
+// issue #173
+fn if_suggestion() {
+ let x = "test";
+ match x {
+ "test" => println!(),
+ _ => (),
+ }
+
+ #[derive(PartialEq, Eq)]
+ enum Foo {
+ A,
+ B,
+ C(u32),
+ }
+
+ let x = Foo::A;
+ match x {
+ Foo::A => println!(),
+ _ => (),
+ }
+
+ const FOO_C: Foo = Foo::C(0);
+ match x {
+ FOO_C => println!(),
+ _ => (),
+ }
+
+ match &&x {
+ Foo::A => println!(),
+ _ => (),
+ }
+
+ let x = &x;
+ match &x {
+ Foo::A => println!(),
+ _ => (),
+ }
+
+ enum Bar {
+ A,
+ B,
+ }
+ impl PartialEq for Bar {
+ fn eq(&self, rhs: &Self) -> bool {
+ matches!((self, rhs), (Self::A, Self::A) | (Self::B, Self::B))
+ }
+ }
+ impl Eq for Bar {}
+
+ let x = Bar::A;
+ match x {
+ Bar::A => println!(),
+ _ => (),
+ }
+
+ // issue #7038
+ struct X;
+ let x = Some(X);
+ match x {
+ None => println!(),
+ _ => (),
+ };
+}
+
+// See: issue #8282
+fn ranges() {
+ enum E {
+ V,
+ }
+ let x = (Some(E::V), Some(42));
+
+ // Don't lint, because the `E` enum can be extended with additional fields later. Thus, the
+ // proposed replacement to `if let Some(E::V)` may hide non-exhaustive warnings that appeared
+ // because of `match` construction.
+ match x {
+ (Some(E::V), _) => {},
+ (None, _) => {},
+ }
+
+ // lint
+ match x {
+ (Some(_), _) => {},
+ (None, _) => {},
+ }
+
+ // lint
+ match x {
+ (Some(E::V), _) => todo!(),
+ (_, _) => {},
+ }
+
+ // lint
+ match (Some(42), Some(E::V), Some(42)) {
+ (.., Some(E::V), _) => {},
+ (..) => {},
+ }
+
+ // Don't lint, see above.
+ match (Some(E::V), Some(E::V), Some(E::V)) {
+ (.., Some(E::V), _) => {},
+ (.., None, _) => {},
+ }
+
+ // Don't lint, see above.
+ match (Some(E::V), Some(E::V), Some(E::V)) {
+ (Some(E::V), ..) => {},
+ (None, ..) => {},
+ }
+
+ // Don't lint, see above.
+ match (Some(E::V), Some(E::V), Some(E::V)) {
+ (_, Some(E::V), ..) => {},
+ (_, None, ..) => {},
+ }
+}
+
+fn skip_type_aliases() {
+ enum OptionEx {
+ Some(i32),
+ None,
+ }
+ enum ResultEx {
+ Err(i32),
+ Ok(i32),
+ }
+
+ use OptionEx::{None, Some};
+ use ResultEx::{Err, Ok};
+
+ // don't lint
+ match Err(42) {
+ Ok(_) => dummy(),
+ Err(_) => (),
+ };
+
+ // don't lint
+ match Some(1i32) {
+ Some(_) => dummy(),
+ None => (),
+ };
+}
+
+macro_rules! single_match {
+ ($num:literal) => {
+ match $num {
+ 15 => println!("15"),
+ _ => (),
+ }
+ };
+}
+
+fn main() {
+ single_match!(5);
+
+ // Don't lint
+ let _ = match Some(0) {
+ #[cfg(feature = "foo")]
+ Some(10) => 11,
+ Some(x) => x,
+ _ => 0,
+ };
+}
--- /dev/null
- --> $DIR/single_match.rs:8:5
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:16:5
++ --> $DIR/single_match.rs:9:5
+ |
+LL | / match x {
+LL | | Some(y) => {
+LL | | println!("{:?}", y);
+LL | | },
+LL | | _ => (),
+LL | | };
+ | |_____^
+ |
+ = note: `-D clippy::single-match` implied by `-D warnings`
+help: try this
+ |
+LL ~ if let Some(y) = x {
+LL + println!("{:?}", y);
+LL ~ };
+ |
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:25:5
++ --> $DIR/single_match.rs:17:5
+ |
+LL | / match x {
+LL | | // Note the missing block braces.
+LL | | // We suggest `if let Some(y) = x { .. }` because the macro
+LL | | // is expanded before we can do anything.
+LL | | Some(y) => println!("{:?}", y),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if let Some(y) = x { println!("{:?}", y) }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:54:5
++ --> $DIR/single_match.rs:26:5
+ |
+LL | / match z {
+LL | | (2..=3, 7..=9) => dummy(),
+LL | | _ => {},
+LL | | };
+ | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:59:5
++ --> $DIR/single_match.rs:55:5
+ |
+LL | / match x {
+LL | | Some(y) => dummy(),
+LL | | None => (),
+LL | | };
+ | |_____^ help: try this: `if let Some(y) = x { dummy() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:66:5
++ --> $DIR/single_match.rs:60:5
+ |
+LL | / match y {
+LL | | Ok(y) => dummy(),
+LL | | Err(..) => (),
+LL | | };
+ | |_____^ help: try this: `if let Ok(y) = y { dummy() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:87:5
++ --> $DIR/single_match.rs:67:5
+ |
+LL | / match c {
+LL | | Cow::Borrowed(..) => dummy(),
+LL | | Cow::Owned(..) => (),
+LL | | };
+ | |_____^ help: try this: `if let Cow::Borrowed(..) = c { dummy() }`
+
+error: you seem to be trying to use `match` for an equality check. Consider using `if`
- --> $DIR/single_match.rs:100:5
++ --> $DIR/single_match.rs:88:5
+ |
+LL | / match x {
+LL | | "test" => println!(),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if x == "test" { println!() }`
+
+error: you seem to be trying to use `match` for an equality check. Consider using `if`
- --> $DIR/single_match.rs:106:5
++ --> $DIR/single_match.rs:101:5
+ |
+LL | / match x {
+LL | | Foo::A => println!(),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if x == Foo::A { println!() }`
+
+error: you seem to be trying to use `match` for an equality check. Consider using `if`
- --> $DIR/single_match.rs:111:5
++ --> $DIR/single_match.rs:107:5
+ |
+LL | / match x {
+LL | | FOO_C => println!(),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if x == FOO_C { println!() }`
+
+error: you seem to be trying to use `match` for an equality check. Consider using `if`
- --> $DIR/single_match.rs:117:5
++ --> $DIR/single_match.rs:112:5
+ |
+LL | / match &&x {
+LL | | Foo::A => println!(),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if x == Foo::A { println!() }`
+
+error: you seem to be trying to use `match` for an equality check. Consider using `if`
- --> $DIR/single_match.rs:134:5
++ --> $DIR/single_match.rs:118:5
+ |
+LL | / match &x {
+LL | | Foo::A => println!(),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if x == &Foo::A { println!() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:142:5
++ --> $DIR/single_match.rs:135:5
+ |
+LL | / match x {
+LL | | Bar::A => println!(),
+LL | | _ => (),
+LL | | }
+ | |_____^ help: try this: `if let Bar::A = x { println!() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:164:5
++ --> $DIR/single_match.rs:143:5
+ |
+LL | / match x {
+LL | | None => println!(),
+LL | | _ => (),
+LL | | };
+ | |_____^ help: try this: `if let None = x { println!() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:170:5
++ --> $DIR/single_match.rs:165:5
+ |
+LL | / match x {
+LL | | (Some(_), _) => {},
+LL | | (None, _) => {},
+LL | | }
+ | |_____^ help: try this: `if let (Some(_), _) = x {}`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match.rs:176:5
++ --> $DIR/single_match.rs:171:5
+ |
+LL | / match x {
+LL | | (Some(E::V), _) => todo!(),
+LL | | (_, _) => {},
+LL | | }
+ | |_____^ help: try this: `if let (Some(E::V), _) = x { todo!() }`
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
++ --> $DIR/single_match.rs:177:5
+ |
+LL | / match (Some(42), Some(E::V), Some(42)) {
+LL | | (.., Some(E::V), _) => {},
+LL | | (..) => {},
+LL | | }
+ | |_____^ help: try this: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}`
+
+error: aborting due to 16 previous errors
+
--- /dev/null
-
+// aux-build: proc_macro_with_span.rs
- #![allow(clippy::needless_return)]
- #![allow(clippy::no_effect)]
+#![warn(clippy::single_match_else)]
++#![allow(clippy::needless_return, clippy::no_effect, clippy::uninlined_format_args)]
+
+extern crate proc_macro_with_span;
+use proc_macro_with_span::with_span;
+
+enum ExprNode {
+ ExprAddrOf,
+ Butterflies,
+ Unicorns,
+}
+
+static NODE: ExprNode = ExprNode::Unicorns;
+
+fn unwrap_addr() -> Option<&'static ExprNode> {
+ let _ = match ExprNode::Butterflies {
+ ExprNode::ExprAddrOf => Some(&NODE),
+ _ => {
+ let x = 5;
+ None
+ },
+ };
+
+ // Don't lint
+ with_span!(span match ExprNode::Butterflies {
+ ExprNode::ExprAddrOf => Some(&NODE),
+ _ => {
+ let x = 5;
+ None
+ },
+ })
+}
+
+macro_rules! unwrap_addr {
+ ($expression:expr) => {
+ match $expression {
+ ExprNode::ExprAddrOf => Some(&NODE),
+ _ => {
+ let x = 5;
+ None
+ },
+ }
+ };
+}
+
+#[rustfmt::skip]
+fn main() {
+ unwrap_addr!(ExprNode::Unicorns);
+
+ //
+ // don't lint single exprs/statements
+ //
+
+ // don't lint here
+ match Some(1) {
+ Some(a) => println!("${:?}", a),
+ None => return,
+ }
+
+ // don't lint here
+ match Some(1) {
+ Some(a) => println!("${:?}", a),
+ None => {
+ return
+ },
+ }
+
+ // don't lint here
+ match Some(1) {
+ Some(a) => println!("${:?}", a),
+ None => {
+ return;
+ },
+ }
+
+ //
+ // lint multiple exprs/statements "else" blocks
+ //
+
+ // lint here
+ match Some(1) {
+ Some(a) => println!("${:?}", a),
+ None => {
+ println!("else block");
+ return
+ },
+ }
+
+ // lint here
+ match Some(1) {
+ Some(a) => println!("${:?}", a),
+ None => {
+ println!("else block");
+ return;
+ },
+ }
+
+ // lint here
+ use std::convert::Infallible;
+ match Result::<i32, Infallible>::Ok(1) {
+ Ok(a) => println!("${:?}", a),
+ Err(_) => {
+ println!("else block");
+ return;
+ }
+ }
+
+ use std::borrow::Cow;
+ match Cow::from("moo") {
+ Cow::Owned(a) => println!("${:?}", a),
+ Cow::Borrowed(_) => {
+ println!("else block");
+ return;
+ }
+ }
+}
--- /dev/null
- --> $DIR/single_match_else.rs:19:13
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match_else.rs:84:5
++ --> $DIR/single_match_else.rs:17:13
+ |
+LL | let _ = match ExprNode::Butterflies {
+ | _____________^
+LL | | ExprNode::ExprAddrOf => Some(&NODE),
+LL | | _ => {
+LL | | let x = 5;
+LL | | None
+LL | | },
+LL | | };
+ | |_____^
+ |
+ = note: `-D clippy::single-match-else` implied by `-D warnings`
+help: try this
+ |
+LL ~ let _ = if let ExprNode::ExprAddrOf = ExprNode::Butterflies { Some(&NODE) } else {
+LL + let x = 5;
+LL + None
+LL ~ };
+ |
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match_else.rs:93:5
++ --> $DIR/single_match_else.rs:82:5
+ |
+LL | / match Some(1) {
+LL | | Some(a) => println!("${:?}", a),
+LL | | None => {
+LL | | println!("else block");
+LL | | return
+LL | | },
+LL | | }
+ | |_____^
+ |
+help: try this
+ |
+LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else {
+LL + println!("else block");
+LL + return
+LL + }
+ |
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match_else.rs:103:5
++ --> $DIR/single_match_else.rs:91:5
+ |
+LL | / match Some(1) {
+LL | | Some(a) => println!("${:?}", a),
+LL | | None => {
+LL | | println!("else block");
+LL | | return;
+LL | | },
+LL | | }
+ | |_____^
+ |
+help: try this
+ |
+LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else {
+LL + println!("else block");
+LL + return;
+LL + }
+ |
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
- --> $DIR/single_match_else.rs:112:5
++ --> $DIR/single_match_else.rs:101:5
+ |
+LL | / match Result::<i32, Infallible>::Ok(1) {
+LL | | Ok(a) => println!("${:?}", a),
+LL | | Err(_) => {
+LL | | println!("else block");
+LL | | return;
+LL | | }
+LL | | }
+ | |_____^
+ |
+help: try this
+ |
+LL ~ if let Ok(a) = Result::<i32, Infallible>::Ok(1) { println!("${:?}", a) } else {
+LL + println!("else block");
+LL + return;
+LL + }
+ |
+
+error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
++ --> $DIR/single_match_else.rs:110:5
+ |
+LL | / match Cow::from("moo") {
+LL | | Cow::Owned(a) => println!("${:?}", a),
+LL | | Cow::Borrowed(_) => {
+LL | | println!("else block");
+LL | | return;
+LL | | }
+LL | | }
+ | |_____^
+ |
+help: try this
+ |
+LL ~ if let Cow::Owned(a) = Cow::from("moo") { println!("${:?}", a) } else {
+LL + println!("else block");
+LL + return;
+LL + }
+ |
+
+error: aborting due to 5 previous errors
+
--- /dev/null
+#![warn(clippy::std_instead_of_core)]
+#![allow(unused_imports)]
+
+extern crate alloc;
+
+#[warn(clippy::std_instead_of_core)]
+fn std_instead_of_core() {
+ // Regular import
+ use std::hash::Hasher;
+ // Absolute path
+ use ::std::hash::Hash;
+ // Don't lint on `env` macro
+ use std::env;
+
+ // Multiple imports
+ use std::fmt::{Debug, Result};
+
+ // Function calls
+ let ptr = std::ptr::null::<u32>();
+ let ptr_mut = ::std::ptr::null_mut::<usize>();
+
+ // Types
+ let cell = std::cell::Cell::new(8u32);
+ let cell_absolute = ::std::cell::Cell::new(8u32);
+
+ let _ = std::env!("PATH");
++
++ // do not lint until `error_in_core` is stable
++ use std::error::Error;
++
++ // lint items re-exported from private modules, `core::iter::traits::iterator::Iterator`
++ use std::iter::Iterator;
+}
+
+#[warn(clippy::std_instead_of_alloc)]
+fn std_instead_of_alloc() {
+ // Only lint once.
+ use std::vec;
+ use std::vec::Vec;
+}
+
+#[warn(clippy::alloc_instead_of_core)]
+fn alloc_instead_of_core() {
+ use alloc::slice::from_ref;
+}
+
+fn main() {
+ std_instead_of_core();
+ std_instead_of_alloc();
+ alloc_instead_of_core();
+}
--- /dev/null
- error: used import from `std` instead of `alloc`
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:9:9
+ |
+LL | use std::hash::Hasher;
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+ = note: `-D clippy::std-instead-of-core` implied by `-D warnings`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:11:9
+ |
+LL | use ::std::hash::Hash;
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:16:20
+ |
+LL | use std::fmt::{Debug, Result};
+ | ^^^^^
+ |
+ = help: consider importing the item from `core`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:16:27
+ |
+LL | use std::fmt::{Debug, Result};
+ | ^^^^^^
+ |
+ = help: consider importing the item from `core`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:19:15
+ |
+LL | let ptr = std::ptr::null::<u32>();
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:20:19
+ |
+LL | let ptr_mut = ::std::ptr::null_mut::<usize>();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:23:16
+ |
+LL | let cell = std::cell::Cell::new(8u32);
+ | ^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+
+error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:24:25
+ |
+LL | let cell_absolute = ::std::cell::Cell::new(8u32);
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+
- --> $DIR/std_instead_of_core.rs:33:9
++error: used import from `std` instead of `core`
+ --> $DIR/std_instead_of_core.rs:32:9
+ |
++LL | use std::iter::Iterator;
++ | ^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider importing the item from `core`
++
++error: used import from `std` instead of `alloc`
++ --> $DIR/std_instead_of_core.rs:38:9
++ |
+LL | use std::vec;
+ | ^^^^^^^^
+ |
+ = help: consider importing the item from `alloc`
+ = note: `-D clippy::std-instead-of-alloc` implied by `-D warnings`
+
+error: used import from `std` instead of `alloc`
- --> $DIR/std_instead_of_core.rs:38:9
++ --> $DIR/std_instead_of_core.rs:39:9
+ |
+LL | use std::vec::Vec;
+ | ^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `alloc`
+
+error: used import from `alloc` instead of `core`
- error: aborting due to 11 previous errors
++ --> $DIR/std_instead_of_core.rs:44:9
+ |
+LL | use alloc::slice::from_ref;
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider importing the item from `core`
+ = note: `-D clippy::alloc-instead-of-core` implied by `-D warnings`
+
++error: aborting due to 12 previous errors
+
--- /dev/null
-
+// run-rustfix
+// aux-build:macro_rules.rs
+#![warn(clippy::toplevel_ref_arg)]
++#![allow(clippy::uninlined_format_args)]
+
+#[macro_use]
+extern crate macro_rules;
+
+macro_rules! gen_binding {
+ () => {
+ let _y = &42;
+ };
+}
+
+fn main() {
+ // Closures should not warn
+ let y = |ref x| println!("{:?}", x);
+ y(1u8);
+
+ let _x = &1;
+
+ let _y: &(&_, u8) = &(&1, 2);
+
+ let _z = &(1 + 2);
+
+ let _z = &mut (1 + 2);
+
+ let (ref x, _) = (1, 2); // ok, not top level
+ println!("The answer is {}.", x);
+
+ let _x = &vec![1, 2, 3];
+
+ // Make sure that allowing the lint works
+ #[allow(clippy::toplevel_ref_arg)]
+ let ref mut _x = 1_234_543;
+
+ // ok
+ for ref _x in 0..10 {}
+
+ // lint in macro
+ #[allow(unused)]
+ {
+ gen_binding!();
+ }
+
+ // do not lint in external macro
+ {
+ ref_arg_binding!();
+ }
+}
--- /dev/null
-
+// run-rustfix
+// aux-build:macro_rules.rs
+#![warn(clippy::toplevel_ref_arg)]
++#![allow(clippy::uninlined_format_args)]
+
+#[macro_use]
+extern crate macro_rules;
+
+macro_rules! gen_binding {
+ () => {
+ let ref _y = 42;
+ };
+}
+
+fn main() {
+ // Closures should not warn
+ let y = |ref x| println!("{:?}", x);
+ y(1u8);
+
+ let ref _x = 1;
+
+ let ref _y: (&_, u8) = (&1, 2);
+
+ let ref _z = 1 + 2;
+
+ let ref mut _z = 1 + 2;
+
+ let (ref x, _) = (1, 2); // ok, not top level
+ println!("The answer is {}.", x);
+
+ let ref _x = vec![1, 2, 3];
+
+ // Make sure that allowing the lint works
+ #[allow(clippy::toplevel_ref_arg)]
+ let ref mut _x = 1_234_543;
+
+ // ok
+ for ref _x in 0..10 {}
+
+ // lint in macro
+ #[allow(unused)]
+ {
+ gen_binding!();
+ }
+
+ // do not lint in external macro
+ {
+ ref_arg_binding!();
+ }
+}
--- /dev/null
-
+// normalize-stderr-test "\(\d+ byte\)" -> "(N byte)"
+// normalize-stderr-test "\(limit: \d+ byte\)" -> "(limit: N byte)"
- #![allow(clippy::disallowed_names, clippy::redundant_field_names)]
+#![deny(clippy::trivially_copy_pass_by_ref)]
++#![allow(
++ clippy::disallowed_names,
++ 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:47: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:4:9
++ --> $DIR/trivially_copy_pass_by_ref.rs:50: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:47:20
++ --> $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:47:29
++ --> $DIR/trivially_copy_pass_by_ref.rs:50: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:54:12
++ --> $DIR/trivially_copy_pass_by_ref.rs:50: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:54:22
++ --> $DIR/trivially_copy_pass_by_ref.rs:57: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:54:31
++ --> $DIR/trivially_copy_pass_by_ref.rs:57: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:54:40
++ --> $DIR/trivially_copy_pass_by_ref.rs:57: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:56:16
++ --> $DIR/trivially_copy_pass_by_ref.rs:57: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:56:25
++ --> $DIR/trivially_copy_pass_by_ref.rs:59: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:56:34
++ --> $DIR/trivially_copy_pass_by_ref.rs:59: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:58:35
++ --> $DIR/trivially_copy_pass_by_ref.rs:59: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:70:16
++ --> $DIR/trivially_copy_pass_by_ref.rs:61: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:70:25
++ --> $DIR/trivially_copy_pass_by_ref.rs:73: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:70:34
++ --> $DIR/trivially_copy_pass_by_ref.rs:73: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:74:34
++ --> $DIR/trivially_copy_pass_by_ref.rs:73: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:106:21
++ --> $DIR/trivially_copy_pass_by_ref.rs:77: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:111:15
++ --> $DIR/trivially_copy_pass_by_ref.rs:109: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:138:37
++ --> $DIR/trivially_copy_pass_by_ref.rs:114: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:141: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
+#![warn(clippy::uninit_vec)]
+
+use std::mem::MaybeUninit;
+
+#[derive(Default)]
+struct MyVec {
+ vec: Vec<u8>,
+}
+
+fn main() {
+ // with_capacity() -> set_len() should be detected
+ let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ unsafe {
+ vec.set_len(200);
+ }
+
+ // reserve() -> set_len() should be detected
+ vec.reserve(1000);
+ unsafe {
+ vec.set_len(200);
+ }
+
+ // new() -> set_len() should be detected
+ let mut vec: Vec<u8> = Vec::new();
+ unsafe {
+ vec.set_len(200);
+ }
+
+ // default() -> set_len() should be detected
+ let mut vec: Vec<u8> = Default::default();
+ unsafe {
+ vec.set_len(200);
+ }
+
+ let mut vec: Vec<u8> = Vec::default();
+ unsafe {
+ vec.set_len(200);
+ }
+
+ // test when both calls are enclosed in the same unsafe block
+ unsafe {
+ let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ vec.set_len(200);
+
+ vec.reserve(1000);
+ vec.set_len(200);
+ }
+
+ let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ unsafe {
+ // test the case where there are other statements in the following unsafe block
+ vec.set_len(200);
+ assert!(vec.len() == 200);
+ }
+
+ // handle vec stored in the field of a struct
+ let mut my_vec = MyVec::default();
+ my_vec.vec.reserve(1000);
+ unsafe {
+ my_vec.vec.set_len(200);
+ }
+
+ my_vec.vec = Vec::with_capacity(1000);
+ unsafe {
+ my_vec.vec.set_len(200);
+ }
+
+ // Test `#[allow(...)]` attributes on inner unsafe block (shouldn't trigger)
+ let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ #[allow(clippy::uninit_vec)]
+ unsafe {
+ vec.set_len(200);
+ }
+
+ // MaybeUninit-wrapped types should not be detected
+ unsafe {
+ let mut vec: Vec<MaybeUninit<u8>> = Vec::with_capacity(1000);
+ vec.set_len(200);
+
+ let mut vec: Vec<(MaybeUninit<u8>, MaybeUninit<bool>)> = Vec::with_capacity(1000);
+ vec.set_len(200);
+
+ let mut vec: Vec<(MaybeUninit<u8>, [MaybeUninit<bool>; 2])> = Vec::with_capacity(1000);
+ vec.set_len(200);
+ }
+
+ // known false negative
+ let mut vec1: Vec<u8> = Vec::with_capacity(1000);
+ let mut vec2: Vec<u8> = Vec::with_capacity(1000);
+ unsafe {
+ vec1.set_len(200);
+ vec2.set_len(200);
+ }
++
++ // set_len(0) should not be detected
++ let mut vec: Vec<u8> = Vec::with_capacity(1000);
++ unsafe {
++ vec.set_len(0);
++ }
+}
--- /dev/null
--- /dev/null
++// aux-build:proc_macro_with_span.rs
++// run-rustfix
++#![feature(custom_inner_attributes)]
++#![warn(clippy::uninlined_format_args)]
++#![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)]
++#![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)]
++
++extern crate proc_macro_with_span;
++use proc_macro_with_span::with_span;
++
++macro_rules! no_param_str {
++ () => {
++ "{}"
++ };
++}
++
++macro_rules! my_println {
++ ($($args:tt),*) => {{
++ println!($($args),*)
++ }};
++}
++
++macro_rules! my_println_args {
++ ($($args:tt),*) => {{
++ println!("foo: {}", format_args!($($args),*))
++ }};
++}
++
++fn tester(fn_arg: i32) {
++ let local_i32 = 1;
++ let local_f64 = 2.0;
++ let local_opt: Option<i32> = Some(3);
++ let width = 4;
++ let prec = 5;
++ let val = 6;
++
++ // make sure this file hasn't been corrupted with tabs converted to spaces
++ // let _ = ' '; // <- this is a single tab character
++ let _: &[u8; 3] = b" "; // <- <tab><space><tab>
++
++ println!("val='{local_i32}'");
++ println!("val='{local_i32}'"); // 3 spaces
++ println!("val='{local_i32}'"); // tab
++ println!("val='{local_i32}'"); // space+tab
++ println!("val='{local_i32}'"); // tab+space
++ println!(
++ "val='{local_i32}'"
++ );
++ println!("{local_i32}");
++ println!("{fn_arg}");
++ println!("{local_i32:?}");
++ println!("{local_i32:#?}");
++ println!("{local_i32:4}");
++ println!("{local_i32:04}");
++ println!("{local_i32:<3}");
++ println!("{local_i32:#010x}");
++ println!("{local_f64:.1}");
++ println!("Hello {} is {local_f64:.local_i32$}", "x");
++ println!("Hello {local_i32} is {local_f64:.*}", 5);
++ println!("Hello {local_i32} is {local_f64:.*}", 5);
++ println!("{local_i32} {local_f64}");
++ println!("{local_i32}, {}", local_opt.unwrap());
++ println!("{val}");
++ println!("{val}");
++ println!("{} {1}", local_i32, 42);
++ println!("val='{local_i32}'");
++ println!("val='{local_i32}'");
++ println!("val='{local_i32}'");
++ println!("val='{fn_arg}'");
++ println!("{local_i32}");
++ println!("{local_i32:?}");
++ println!("{local_i32:#?}");
++ println!("{local_i32:04}");
++ println!("{local_i32:<3}");
++ println!("{local_i32:#010x}");
++ println!("{local_f64:.1}");
++ println!("{local_i32} {local_i32}");
++ println!("{local_f64} {local_i32} {local_i32} {local_f64}");
++ println!("{local_i32} {local_f64}");
++ println!("{local_f64} {local_i32}");
++ println!("{local_f64} {local_i32} {local_f64} {local_i32}");
++ println!("{1} {0}", "str", local_i32);
++ println!("{local_i32}");
++ println!("{local_i32:width$}");
++ println!("{local_i32:width$}");
++ println!("{local_i32:.prec$}");
++ println!("{local_i32:.prec$}");
++ println!("{val:val$}");
++ println!("{val:val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{val:val$.val$}");
++ println!("{width:width$}");
++ println!("{local_i32:width$}");
++ println!("{width:width$}");
++ println!("{local_i32:width$}");
++ println!("{prec:.prec$}");
++ println!("{local_i32:.prec$}");
++ println!("{prec:.prec$}");
++ println!("{local_i32:.prec$}");
++ println!("{width:width$.prec$}");
++ println!("{width:width$.prec$}");
++ println!("{local_f64:width$.prec$}");
++ println!("{local_f64:width$.prec$} {local_f64} {width} {prec}");
++ println!(
++ "{local_i32:width$.prec$} {local_i32:prec$.width$} {width:local_i32$.prec$} {width:prec$.local_i32$} {prec:local_i32$.width$} {prec:width$.local_i32$}",
++ );
++ println!(
++ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$} {3}",
++ local_i32,
++ width,
++ prec,
++ 1 + 2
++ );
++ println!("Width = {local_i32}, value with width = {local_f64:local_i32$}");
++ println!("{local_i32:width$.prec$}");
++ println!("{width:width$.prec$}");
++ println!("{}", format!("{local_i32}"));
++ my_println!("{}", local_i32);
++ my_println_args!("{}", local_i32);
++
++ // these should NOT be modified by the lint
++ println!(concat!("nope ", "{}"), local_i32);
++ println!("val='{local_i32}'");
++ println!("val='{local_i32 }'");
++ println!("val='{local_i32 }'"); // with tab
++ println!("val='{local_i32\n}'");
++ println!("{}", usize::MAX);
++ println!("{}", local_opt.unwrap());
++ println!(
++ "val='{local_i32
++ }'"
++ );
++ println!(no_param_str!(), local_i32);
++
++ println!(
++ "{val}",
++ );
++ println!("{val}");
++
++ println!(with_span!("{0} {1}" "{1} {0}"), local_i32, local_f64);
++ println!("{}", with_span!(span val));
++}
++
++fn main() {
++ tester(42);
++}
++
++fn _under_msrv() {
++ #![clippy::msrv = "1.57"]
++ let local_i32 = 1;
++ println!("don't expand='{}'", local_i32);
++}
++
++fn _meets_msrv() {
++ #![clippy::msrv = "1.58"]
++ let local_i32 = 1;
++ println!("expand='{local_i32}'");
++}
--- /dev/null
--- /dev/null
++// aux-build:proc_macro_with_span.rs
++// run-rustfix
++#![feature(custom_inner_attributes)]
++#![warn(clippy::uninlined_format_args)]
++#![allow(named_arguments_used_positionally, unused_imports, unused_macros, unused_variables)]
++#![allow(clippy::eq_op, clippy::format_in_format_args, clippy::print_literal)]
++
++extern crate proc_macro_with_span;
++use proc_macro_with_span::with_span;
++
++macro_rules! no_param_str {
++ () => {
++ "{}"
++ };
++}
++
++macro_rules! my_println {
++ ($($args:tt),*) => {{
++ println!($($args),*)
++ }};
++}
++
++macro_rules! my_println_args {
++ ($($args:tt),*) => {{
++ println!("foo: {}", format_args!($($args),*))
++ }};
++}
++
++fn tester(fn_arg: i32) {
++ let local_i32 = 1;
++ let local_f64 = 2.0;
++ let local_opt: Option<i32> = Some(3);
++ let width = 4;
++ let prec = 5;
++ let val = 6;
++
++ // make sure this file hasn't been corrupted with tabs converted to spaces
++ // let _ = ' '; // <- this is a single tab character
++ let _: &[u8; 3] = b" "; // <- <tab><space><tab>
++
++ println!("val='{}'", local_i32);
++ println!("val='{ }'", local_i32); // 3 spaces
++ println!("val='{ }'", local_i32); // tab
++ println!("val='{ }'", local_i32); // space+tab
++ println!("val='{ }'", local_i32); // tab+space
++ println!(
++ "val='{
++ }'",
++ local_i32
++ );
++ println!("{}", local_i32);
++ println!("{}", fn_arg);
++ println!("{:?}", local_i32);
++ println!("{:#?}", local_i32);
++ println!("{:4}", local_i32);
++ println!("{:04}", local_i32);
++ println!("{:<3}", local_i32);
++ println!("{:#010x}", local_i32);
++ println!("{:.1}", local_f64);
++ println!("Hello {} is {:.*}", "x", local_i32, local_f64);
++ println!("Hello {} is {:.*}", local_i32, 5, local_f64);
++ println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
++ println!("{} {}", local_i32, local_f64);
++ println!("{}, {}", local_i32, local_opt.unwrap());
++ println!("{}", val);
++ println!("{}", v = val);
++ println!("{} {1}", local_i32, 42);
++ println!("val='{\t }'", local_i32);
++ println!("val='{\n }'", local_i32);
++ println!("val='{local_i32}'", local_i32 = local_i32);
++ println!("val='{local_i32}'", local_i32 = fn_arg);
++ println!("{0}", local_i32);
++ println!("{0:?}", local_i32);
++ println!("{0:#?}", local_i32);
++ println!("{0:04}", local_i32);
++ println!("{0:<3}", local_i32);
++ println!("{0:#010x}", local_i32);
++ println!("{0:.1}", local_f64);
++ println!("{0} {0}", local_i32);
++ println!("{1} {} {0} {}", local_i32, local_f64);
++ println!("{0} {1}", local_i32, local_f64);
++ println!("{1} {0}", local_i32, local_f64);
++ println!("{1} {0} {1} {0}", local_i32, local_f64);
++ println!("{1} {0}", "str", local_i32);
++ println!("{v}", v = local_i32);
++ println!("{local_i32:0$}", width);
++ println!("{local_i32:w$}", w = width);
++ println!("{local_i32:.0$}", prec);
++ println!("{local_i32:.p$}", p = prec);
++ println!("{:0$}", v = val);
++ println!("{0:0$}", v = val);
++ println!("{:0$.0$}", v = val);
++ println!("{0:0$.0$}", v = val);
++ println!("{0:0$.v$}", v = val);
++ println!("{0:v$.0$}", v = val);
++ println!("{v:0$.0$}", v = val);
++ println!("{v:v$.0$}", v = val);
++ println!("{v:0$.v$}", v = val);
++ println!("{v:v$.v$}", v = val);
++ println!("{:0$}", width);
++ println!("{:1$}", local_i32, width);
++ println!("{:w$}", w = width);
++ println!("{:w$}", local_i32, w = width);
++ println!("{:.0$}", prec);
++ println!("{:.1$}", local_i32, prec);
++ println!("{:.p$}", p = prec);
++ println!("{:.p$}", local_i32, p = prec);
++ println!("{:0$.1$}", width, prec);
++ println!("{:0$.w$}", width, w = prec);
++ println!("{:1$.2$}", local_f64, width, prec);
++ println!("{:1$.2$} {0} {1} {2}", local_f64, width, prec);
++ println!(
++ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}",
++ local_i32, width, prec,
++ );
++ println!(
++ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$} {3}",
++ local_i32,
++ width,
++ prec,
++ 1 + 2
++ );
++ println!("Width = {}, value with width = {:0$}", local_i32, local_f64);
++ println!("{:w$.p$}", local_i32, w = width, p = prec);
++ println!("{:w$.p$}", w = width, p = prec);
++ println!("{}", format!("{}", local_i32));
++ my_println!("{}", local_i32);
++ my_println_args!("{}", local_i32);
++
++ // these should NOT be modified by the lint
++ println!(concat!("nope ", "{}"), local_i32);
++ println!("val='{local_i32}'");
++ println!("val='{local_i32 }'");
++ println!("val='{local_i32 }'"); // with tab
++ println!("val='{local_i32\n}'");
++ println!("{}", usize::MAX);
++ println!("{}", local_opt.unwrap());
++ println!(
++ "val='{local_i32
++ }'"
++ );
++ println!(no_param_str!(), local_i32);
++
++ println!(
++ "{}",
++ // comment with a comma , in it
++ val,
++ );
++ println!("{}", /* comment with a comma , in it */ val);
++
++ println!(with_span!("{0} {1}" "{1} {0}"), local_i32, local_f64);
++ println!("{}", with_span!(span val));
++}
++
++fn main() {
++ tester(42);
++}
++
++fn _under_msrv() {
++ #![clippy::msrv = "1.57"]
++ let local_i32 = 1;
++ println!("don't expand='{}'", local_i32);
++}
++
++fn _meets_msrv() {
++ #![clippy::msrv = "1.58"]
++ let local_i32 = 1;
++ println!("expand='{}'", local_i32);
++}
--- /dev/null
--- /dev/null
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:41:5
++ |
++LL | println!("val='{}'", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::uninlined-format-args` implied by `-D warnings`
++help: change this to
++ |
++LL - println!("val='{}'", local_i32);
++LL + println!("val='{local_i32}'");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:42:5
++ |
++LL | println!("val='{ }'", local_i32); // 3 spaces
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{ }'", local_i32); // 3 spaces
++LL + println!("val='{local_i32}'"); // 3 spaces
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:43:5
++ |
++LL | println!("val='{ }'", local_i32); // tab
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{ }'", local_i32); // tab
++LL + println!("val='{local_i32}'"); // tab
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:44:5
++ |
++LL | println!("val='{ }'", local_i32); // space+tab
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{ }'", local_i32); // space+tab
++LL + println!("val='{local_i32}'"); // space+tab
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:45:5
++ |
++LL | println!("val='{ }'", local_i32); // tab+space
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{ }'", local_i32); // tab+space
++LL + println!("val='{local_i32}'"); // tab+space
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:46:5
++ |
++LL | / println!(
++LL | | "val='{
++LL | | }'",
++LL | | local_i32
++LL | | );
++ | |_____^
++ |
++help: change this to
++ |
++LL - "val='{
++LL + "val='{local_i32}'"
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:51:5
++ |
++LL | println!("{}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}", local_i32);
++LL + println!("{local_i32}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:52:5
++ |
++LL | println!("{}", fn_arg);
++ | ^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}", fn_arg);
++LL + println!("{fn_arg}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:53:5
++ |
++LL | println!("{:?}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:?}", local_i32);
++LL + println!("{local_i32:?}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:54:5
++ |
++LL | println!("{:#?}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:#?}", local_i32);
++LL + println!("{local_i32:#?}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:55:5
++ |
++LL | println!("{:4}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:4}", local_i32);
++LL + println!("{local_i32:4}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:56:5
++ |
++LL | println!("{:04}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:04}", local_i32);
++LL + println!("{local_i32:04}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:57:5
++ |
++LL | println!("{:<3}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:<3}", local_i32);
++LL + println!("{local_i32:<3}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:58:5
++ |
++LL | println!("{:#010x}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:#010x}", local_i32);
++LL + println!("{local_i32:#010x}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:59:5
++ |
++LL | println!("{:.1}", local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:.1}", local_f64);
++LL + println!("{local_f64:.1}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:60:5
++ |
++LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
++LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:61:5
++ |
++LL | println!("Hello {} is {:.*}", local_i32, 5, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("Hello {} is {:.*}", local_i32, 5, local_f64);
++LL + println!("Hello {local_i32} is {local_f64:.*}", 5);
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:62:5
++ |
++LL | println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
++LL + println!("Hello {local_i32} is {local_f64:.*}", 5);
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:63:5
++ |
++LL | println!("{} {}", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{} {}", local_i32, local_f64);
++LL + println!("{local_i32} {local_f64}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:64:5
++ |
++LL | println!("{}, {}", local_i32, local_opt.unwrap());
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}, {}", local_i32, local_opt.unwrap());
++LL + println!("{local_i32}, {}", local_opt.unwrap());
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:65:5
++ |
++LL | println!("{}", val);
++ | ^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}", val);
++LL + println!("{val}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:66:5
++ |
++LL | println!("{}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}", v = val);
++LL + println!("{val}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:68:5
++ |
++LL | println!("val='{/t }'", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{/t }'", local_i32);
++LL + println!("val='{local_i32}'");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:69:5
++ |
++LL | println!("val='{/n }'", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{/n }'", local_i32);
++LL + println!("val='{local_i32}'");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:70:5
++ |
++LL | println!("val='{local_i32}'", local_i32 = local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{local_i32}'", local_i32 = local_i32);
++LL + println!("val='{local_i32}'");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:71:5
++ |
++LL | println!("val='{local_i32}'", local_i32 = fn_arg);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("val='{local_i32}'", local_i32 = fn_arg);
++LL + println!("val='{fn_arg}'");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:72:5
++ |
++LL | println!("{0}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0}", local_i32);
++LL + println!("{local_i32}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:73:5
++ |
++LL | println!("{0:?}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:?}", local_i32);
++LL + println!("{local_i32:?}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:74:5
++ |
++LL | println!("{0:#?}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:#?}", local_i32);
++LL + println!("{local_i32:#?}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:75:5
++ |
++LL | println!("{0:04}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:04}", local_i32);
++LL + println!("{local_i32:04}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:76:5
++ |
++LL | println!("{0:<3}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:<3}", local_i32);
++LL + println!("{local_i32:<3}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:77:5
++ |
++LL | println!("{0:#010x}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:#010x}", local_i32);
++LL + println!("{local_i32:#010x}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:78:5
++ |
++LL | println!("{0:.1}", local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:.1}", local_f64);
++LL + println!("{local_f64:.1}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:79:5
++ |
++LL | println!("{0} {0}", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0} {0}", local_i32);
++LL + println!("{local_i32} {local_i32}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:80:5
++ |
++LL | println!("{1} {} {0} {}", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{1} {} {0} {}", local_i32, local_f64);
++LL + println!("{local_f64} {local_i32} {local_i32} {local_f64}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:81:5
++ |
++LL | println!("{0} {1}", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0} {1}", local_i32, local_f64);
++LL + println!("{local_i32} {local_f64}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:82:5
++ |
++LL | println!("{1} {0}", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{1} {0}", local_i32, local_f64);
++LL + println!("{local_f64} {local_i32}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:83:5
++ |
++LL | println!("{1} {0} {1} {0}", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{1} {0} {1} {0}", local_i32, local_f64);
++LL + println!("{local_f64} {local_i32} {local_f64} {local_i32}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:85:5
++ |
++LL | println!("{v}", v = local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{v}", v = local_i32);
++LL + println!("{local_i32}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:86:5
++ |
++LL | println!("{local_i32:0$}", width);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{local_i32:0$}", width);
++LL + println!("{local_i32:width$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:87:5
++ |
++LL | println!("{local_i32:w$}", w = width);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{local_i32:w$}", w = width);
++LL + println!("{local_i32:width$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:88:5
++ |
++LL | println!("{local_i32:.0$}", prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{local_i32:.0$}", prec);
++LL + println!("{local_i32:.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:89:5
++ |
++LL | println!("{local_i32:.p$}", p = prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{local_i32:.p$}", p = prec);
++LL + println!("{local_i32:.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:90:5
++ |
++LL | println!("{:0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:0$}", v = val);
++LL + println!("{val:val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:91:5
++ |
++LL | println!("{0:0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:0$}", v = val);
++LL + println!("{val:val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:92:5
++ |
++LL | println!("{:0$.0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:0$.0$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:93:5
++ |
++LL | println!("{0:0$.0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:0$.0$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:94:5
++ |
++LL | println!("{0:0$.v$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:0$.v$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:95:5
++ |
++LL | println!("{0:v$.0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{0:v$.0$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:96:5
++ |
++LL | println!("{v:0$.0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{v:0$.0$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:97:5
++ |
++LL | println!("{v:v$.0$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{v:v$.0$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:98:5
++ |
++LL | println!("{v:0$.v$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{v:0$.v$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:99:5
++ |
++LL | println!("{v:v$.v$}", v = val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{v:v$.v$}", v = val);
++LL + println!("{val:val$.val$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:100:5
++ |
++LL | println!("{:0$}", width);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:0$}", width);
++LL + println!("{width:width$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:101:5
++ |
++LL | println!("{:1$}", local_i32, width);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:1$}", local_i32, width);
++LL + println!("{local_i32:width$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:102:5
++ |
++LL | println!("{:w$}", w = width);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:w$}", w = width);
++LL + println!("{width:width$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:103:5
++ |
++LL | println!("{:w$}", local_i32, w = width);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:w$}", local_i32, w = width);
++LL + println!("{local_i32:width$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:104:5
++ |
++LL | println!("{:.0$}", prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:.0$}", prec);
++LL + println!("{prec:.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:105:5
++ |
++LL | println!("{:.1$}", local_i32, prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:.1$}", local_i32, prec);
++LL + println!("{local_i32:.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:106:5
++ |
++LL | println!("{:.p$}", p = prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:.p$}", p = prec);
++LL + println!("{prec:.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:107:5
++ |
++LL | println!("{:.p$}", local_i32, p = prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:.p$}", local_i32, p = prec);
++LL + println!("{local_i32:.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:108:5
++ |
++LL | println!("{:0$.1$}", width, prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:0$.1$}", width, prec);
++LL + println!("{width:width$.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:109:5
++ |
++LL | println!("{:0$.w$}", width, w = prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:0$.w$}", width, w = prec);
++LL + println!("{width:width$.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:110:5
++ |
++LL | println!("{:1$.2$}", local_f64, width, prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:1$.2$}", local_f64, width, prec);
++LL + println!("{local_f64:width$.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:111:5
++ |
++LL | println!("{:1$.2$} {0} {1} {2}", local_f64, width, prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:1$.2$} {0} {1} {2}", local_f64, width, prec);
++LL + println!("{local_f64:width$.prec$} {local_f64} {width} {prec}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:112:5
++ |
++LL | / println!(
++LL | | "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}",
++LL | | local_i32, width, prec,
++LL | | );
++ | |_____^
++ |
++help: change this to
++ |
++LL ~ "{local_i32:width$.prec$} {local_i32:prec$.width$} {width:local_i32$.prec$} {width:prec$.local_i32$} {prec:local_i32$.width$} {prec:width$.local_i32$}", width, prec,
++LL ~ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
++LL ~ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
++LL ~ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
++LL ~ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}", width, prec,
++LL ~ "{0:1$.2$} {0:2$.1$} {1:0$.2$} {1:2$.0$} {2:0$.1$} {2:1$.0$}",
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:123:5
++ |
++LL | println!("Width = {}, value with width = {:0$}", local_i32, local_f64);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("Width = {}, value with width = {:0$}", local_i32, local_f64);
++LL + println!("Width = {local_i32}, value with width = {local_f64:local_i32$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:124:5
++ |
++LL | println!("{:w$.p$}", local_i32, w = width, p = prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:w$.p$}", local_i32, w = width, p = prec);
++LL + println!("{local_i32:width$.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:125:5
++ |
++LL | println!("{:w$.p$}", w = width, p = prec);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{:w$.p$}", w = width, p = prec);
++LL + println!("{width:width$.prec$}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:126:20
++ |
++LL | println!("{}", format!("{}", local_i32));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}", format!("{}", local_i32));
++LL + println!("{}", format!("{local_i32}"));
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:144:5
++ |
++LL | / println!(
++LL | | "{}",
++LL | | // comment with a comma , in it
++LL | | val,
++LL | | );
++ | |_____^
++ |
++help: change this to
++ |
++LL - "{}",
++LL + "{val}",
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:149:5
++ |
++LL | println!("{}", /* comment with a comma , in it */ val);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("{}", /* comment with a comma , in it */ val);
++LL + println!("{val}");
++ |
++
++error: variables can be used directly in the `format!` string
++ --> $DIR/uninlined_format_args.rs:168:5
++ |
++LL | println!("expand='{}'", local_i32);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: change this to
++ |
++LL - println!("expand='{}'", local_i32);
++LL + println!("expand='{local_i32}'");
++ |
++
++error: aborting due to 73 previous errors
++
--- /dev/null
-
+// aux-build: proc_macro_with_span.rs
- unused_must_use,
- unused_variables,
- clippy::unused_unit,
- clippy::unnecessary_wraps,
+#![warn(clippy::unit_arg)]
++#![allow(unused_must_use, unused_variables)]
+#![allow(
++ clippy::let_unit_value,
++ clippy::needless_question_mark,
++ clippy::never_loop,
+ clippy::no_effect,
- clippy::needless_question_mark,
+ clippy::or_fun_call,
- clippy::let_unit_value,
- clippy::never_loop
+ clippy::self_named_constructors,
++ clippy::uninlined_format_args,
++ clippy::unnecessary_wraps,
++ clippy::unused_unit
+)]
+
+extern crate proc_macro_with_span;
+
+use proc_macro_with_span::with_span;
+use std::fmt::Debug;
+
+fn foo<T: Debug>(t: T) {
+ println!("{:?}", t);
+}
+
+fn foo3<T1: Debug, T2: Debug, T3: Debug>(t1: T1, t2: T2, t3: T3) {
+ println!("{:?}, {:?}, {:?}", t1, t2, t3);
+}
+
+struct Bar;
+
+impl Bar {
+ fn bar<T: Debug>(&self, t: T) {
+ println!("{:?}", t);
+ }
+}
+
+fn baz<T: Debug>(t: T) {
+ foo(t);
+}
+
+trait Tr {
+ type Args;
+ fn do_it(args: Self::Args);
+}
+
+struct A;
+impl Tr for A {
+ type Args = ();
+ fn do_it(_: Self::Args) {}
+}
+
+struct B;
+impl Tr for B {
+ type Args = <A as Tr>::Args;
+
+ fn do_it(args: Self::Args) {
+ A::do_it(args)
+ }
+}
+
+fn bad() {
+ foo({
+ 1;
+ });
+ foo(foo(1));
+ foo({
+ foo(1);
+ foo(2);
+ });
+ let b = Bar;
+ b.bar({
+ 1;
+ });
+ taking_multiple_units(foo(0), foo(1));
+ taking_multiple_units(foo(0), {
+ foo(1);
+ foo(2);
+ });
+ taking_multiple_units(
+ {
+ foo(0);
+ foo(1);
+ },
+ {
+ foo(2);
+ foo(3);
+ },
+ );
+ // here Some(foo(2)) isn't the top level statement expression, wrap the suggestion in a block
+ None.or(Some(foo(2)));
+ // in this case, the suggestion can be inlined, no need for a surrounding block
+ // foo(()); foo(()) instead of { foo(()); foo(()) }
+ foo(foo(()));
+}
+
+fn ok() {
+ foo(());
+ foo(1);
+ foo({ 1 });
+ foo3("a", 3, vec![3]);
+ let b = Bar;
+ b.bar({ 1 });
+ b.bar(());
+ question_mark();
+ let named_unit_arg = ();
+ foo(named_unit_arg);
+ baz(());
+ B::do_it(());
+}
+
+fn question_mark() -> Result<(), ()> {
+ Ok(Ok(())?)?;
+ Ok(Ok(()))??;
+ Ok(())
+}
+
+#[allow(dead_code)]
+mod issue_2945 {
+ fn unit_fn() -> Result<(), i32> {
+ Ok(())
+ }
+
+ fn fallible() -> Result<(), i32> {
+ Ok(unit_fn()?)
+ }
+}
+
+#[allow(dead_code)]
+fn returning_expr() -> Option<()> {
+ Some(foo(1))
+}
+
+fn taking_multiple_units(a: (), b: ()) {}
+
+fn proc_macro() {
+ with_span!(span taking_multiple_units(unsafe { (); }, 'x: loop { break 'x (); }));
+}
+
+fn main() {
+ bad();
+ ok();
+}
--- /dev/null
- --> $DIR/unit_arg.rs:63:5
+error: passing a unit value to a function
- --> $DIR/unit_arg.rs:66:5
++ --> $DIR/unit_arg.rs:62:5
+ |
+LL | / foo({
+LL | | 1;
+LL | | });
+ | |______^
+ |
+ = note: `-D clippy::unit-arg` implied by `-D warnings`
+help: remove the semicolon from the last statement in the block
+ |
+LL | 1
+ |
+help: or move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ {
+LL + 1;
+LL + };
+LL ~ foo(());
+ |
+
+error: passing a unit value to a function
- --> $DIR/unit_arg.rs:67:5
++ --> $DIR/unit_arg.rs:65:5
+ |
+LL | foo(foo(1));
+ | ^^^^^^^^^^^
+ |
+help: move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ foo(1);
+LL ~ foo(());
+ |
+
+error: passing a unit value to a function
- --> $DIR/unit_arg.rs:72:5
++ --> $DIR/unit_arg.rs:66:5
+ |
+LL | / foo({
+LL | | foo(1);
+LL | | foo(2);
+LL | | });
+ | |______^
+ |
+help: remove the semicolon from the last statement in the block
+ |
+LL | foo(2)
+ |
+help: or move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ {
+LL + foo(1);
+LL + foo(2);
+LL + };
+LL ~ foo(());
+ |
+
+error: passing a unit value to a function
- --> $DIR/unit_arg.rs:75:5
++ --> $DIR/unit_arg.rs:71:5
+ |
+LL | / b.bar({
+LL | | 1;
+LL | | });
+ | |______^
+ |
+help: remove the semicolon from the last statement in the block
+ |
+LL | 1
+ |
+help: or move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ {
+LL + 1;
+LL + };
+LL ~ b.bar(());
+ |
+
+error: passing unit values to a function
- --> $DIR/unit_arg.rs:76:5
++ --> $DIR/unit_arg.rs:74:5
+ |
+LL | taking_multiple_units(foo(0), foo(1));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: move the expressions in front of the call and replace them with the unit literal `()`
+ |
+LL ~ foo(0);
+LL + foo(1);
+LL ~ taking_multiple_units((), ());
+ |
+
+error: passing unit values to a function
- --> $DIR/unit_arg.rs:80:5
++ --> $DIR/unit_arg.rs:75:5
+ |
+LL | / taking_multiple_units(foo(0), {
+LL | | foo(1);
+LL | | foo(2);
+LL | | });
+ | |______^
+ |
+help: remove the semicolon from the last statement in the block
+ |
+LL | foo(2)
+ |
+help: or move the expressions in front of the call and replace them with the unit literal `()`
+ |
+LL ~ foo(0);
+LL + {
+LL + foo(1);
+LL + foo(2);
+LL + };
+LL ~ taking_multiple_units((), ());
+ |
+
+error: passing unit values to a function
- --> $DIR/unit_arg.rs:91:13
++ --> $DIR/unit_arg.rs:79:5
+ |
+LL | / taking_multiple_units(
+LL | | {
+LL | | foo(0);
+LL | | foo(1);
+... |
+LL | | },
+LL | | );
+ | |_____^
+ |
+help: remove the semicolon from the last statement in the block
+ |
+LL | foo(1)
+ |
+help: remove the semicolon from the last statement in the block
+ |
+LL | foo(3)
+ |
+help: or move the expressions in front of the call and replace them with the unit literal `()`
+ |
+LL ~ {
+LL + foo(0);
+LL + foo(1);
+LL + };
+LL + {
+LL + foo(2);
+LL + foo(3);
+LL + };
+LL + taking_multiple_units(
+LL + (),
+LL + (),
+LL ~ );
+ |
+
+error: passing a unit value to a function
- --> $DIR/unit_arg.rs:94:5
++ --> $DIR/unit_arg.rs:90:13
+ |
+LL | None.or(Some(foo(2)));
+ | ^^^^^^^^^^^^
+ |
+help: move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ None.or({
+LL + foo(2);
+LL + Some(())
+LL ~ });
+ |
+
+error: passing a unit value to a function
- --> $DIR/unit_arg.rs:131:5
++ --> $DIR/unit_arg.rs:93:5
+ |
+LL | foo(foo(()));
+ | ^^^^^^^^^^^^
+ |
+help: move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ foo(());
+LL ~ foo(());
+ |
+
+error: passing a unit value to a function
++ --> $DIR/unit_arg.rs:130:5
+ |
+LL | Some(foo(1))
+ | ^^^^^^^^^^^^
+ |
+help: move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ foo(1);
+LL + Some(())
+ |
+
+error: aborting due to 10 previous errors
+
--- /dev/null
- #![allow(clippy::no_effect, unused_must_use, unused_variables)]
+// run-rustfix
+#![warn(clippy::unit_arg)]
++#![allow(unused_must_use, unused_variables)]
++#![allow(clippy::no_effect, clippy::uninlined_format_args)]
+
+use std::fmt::Debug;
+
+fn foo<T: Debug>(t: T) {
+ println!("{:?}", t);
+}
+
+fn foo3<T1: Debug, T2: Debug, T3: Debug>(t1: T1, t2: T2, t3: T3) {
+ println!("{:?}, {:?}, {:?}", t1, t2, t3);
+}
+
+fn bad() {
+ foo(());
+ foo3((), 2, 2);
+ foo(0);
+ taking_two_units((), ());
+ foo(0);
+ foo(1);
+ taking_three_units((), (), ());
+}
+
+fn taking_two_units(a: (), b: ()) {}
+fn taking_three_units(a: (), b: (), c: ()) {}
+
+fn main() {
+ bad();
+}
--- /dev/null
- #![allow(clippy::no_effect, unused_must_use, unused_variables)]
+// run-rustfix
+#![warn(clippy::unit_arg)]
++#![allow(unused_must_use, unused_variables)]
++#![allow(clippy::no_effect, clippy::uninlined_format_args)]
+
+use std::fmt::Debug;
+
+fn foo<T: Debug>(t: T) {
+ println!("{:?}", t);
+}
+
+fn foo3<T1: Debug, T2: Debug, T3: Debug>(t1: T1, t2: T2, t3: T3) {
+ println!("{:?}, {:?}, {:?}", t1, t2, t3);
+}
+
+fn bad() {
+ foo({});
+ foo3({}, 2, 2);
+ taking_two_units({}, foo(0));
+ taking_three_units({}, foo(0), foo(1));
+}
+
+fn taking_two_units(a: (), b: ()) {}
+fn taking_three_units(a: (), b: (), c: ()) {}
+
+fn main() {
+ bad();
+}
--- /dev/null
- --> $DIR/unit_arg_empty_blocks.rs:16:5
+error: passing a unit value to a function
- --> $DIR/unit_arg_empty_blocks.rs:17:5
++ --> $DIR/unit_arg_empty_blocks.rs:17:5
+ |
+LL | foo({});
+ | ^^^^--^
+ | |
+ | help: use a unit literal instead: `()`
+ |
+ = note: `-D clippy::unit-arg` implied by `-D warnings`
+
+error: passing a unit value to a function
- --> $DIR/unit_arg_empty_blocks.rs:18:5
++ --> $DIR/unit_arg_empty_blocks.rs:18:5
+ |
+LL | foo3({}, 2, 2);
+ | ^^^^^--^^^^^^^
+ | |
+ | help: use a unit literal instead: `()`
+
+error: passing unit values to a function
- --> $DIR/unit_arg_empty_blocks.rs:19:5
++ --> $DIR/unit_arg_empty_blocks.rs:19:5
+ |
+LL | taking_two_units({}, foo(0));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: move the expression in front of the call and replace it with the unit literal `()`
+ |
+LL ~ foo(0);
+LL ~ taking_two_units((), ());
+ |
+
+error: passing unit values to a function
++ --> $DIR/unit_arg_empty_blocks.rs:20:5
+ |
+LL | taking_three_units({}, foo(0), foo(1));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: move the expressions in front of the call and replace them with the unit literal `()`
+ |
+LL ~ foo(0);
+LL + foo(1);
+LL ~ taking_three_units((), (), ());
+ |
+
+error: aborting due to 4 previous errors
+
--- /dev/null
+// run-rustfix
+#![warn(clippy::unnecessary_cast)]
+#![allow(
+ unused_must_use,
+ clippy::borrow_as_ptr,
+ clippy::no_effect,
+ clippy::nonstandard_macro_braces,
+ clippy::unnecessary_operation
+)]
+
+#[rustfmt::skip]
+fn main() {
+ // Test cast_unnecessary
+ 1_i32;
+ 1_f32;
+ false;
+ &1i32 as &i32;
+
+ -1_i32;
+ - 1_i32;
+ -1_f32;
+ 1_i32;
+ 1_f32;
+
+ // macro version
+ macro_rules! foo {
+ ($a:ident, $b:ident) => {
+ #[allow(unused)]
+ pub fn $a() -> $b {
+ 1 as $b
+ }
+ };
+ }
+ foo!(a, i32);
+ foo!(b, f32);
+ foo!(c, f64);
+
+ // do not lint cast to cfg-dependant type
+ 1 as std::os::raw::c_char;
+
+ // do not lint cast to alias type
+ 1 as I32Alias;
+ &1 as &I32Alias;
+}
+
+type I32Alias = i32;
+
+mod fixable {
+ #![allow(dead_code)]
+
+ fn main() {
+ // casting integer literal to float is unnecessary
+ 100_f32;
+ 100_f64;
+ 100_f64;
+ let _ = -100_f32;
+ let _ = -100_f64;
+ let _ = -100_f64;
+ 100_f32;
+ 100_f64;
+ // Should not trigger
+ #[rustfmt::skip]
+ let v = vec!(1);
+ &v as &[i32];
+ 0x10 as f32;
+ 0o10 as f32;
+ 0b10 as f32;
+ 0x11 as f64;
+ 0o11 as f64;
+ 0b11 as f64;
+
+ 1_u32;
+ 0x10_i32;
+ 0b10_usize;
+ 0o73_u16;
+ 1_000_000_000_u32;
+
+ 1.0_f64;
+ 0.5_f32;
+
+ 1.0 as u16;
+
+ let _ = -1_i32;
+ let _ = -1.0_f32;
+
+ let _ = 1 as I32Alias;
+ let _ = &1 as &I32Alias;
+ }
+
+ type I32Alias = i32;
+
+ fn issue_9380() {
+ let _: i32 = -1_i32;
+ let _: f32 = -(1) as f32;
+ let _: i64 = -1_i64;
+ let _: i64 = -(1.0) as i64;
+
+ let _ = -(1 + 1) as i64;
+ }
++
++ fn issue_9563() {
++ let _: f64 = (-8.0_f64).exp();
++ #[allow(clippy::precedence)]
++ let _: f64 = -8.0_f64.exp(); // should suggest `-8.0_f64.exp()` here not to change code behavior
++ }
++
++ fn issue_9562_non_literal() {
++ fn foo() -> f32 {
++ 0.
++ }
++
++ let _num = foo();
++ }
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::unnecessary_cast)]
+#![allow(
+ unused_must_use,
+ clippy::borrow_as_ptr,
+ clippy::no_effect,
+ clippy::nonstandard_macro_braces,
+ clippy::unnecessary_operation
+)]
+
+#[rustfmt::skip]
+fn main() {
+ // Test cast_unnecessary
+ 1i32 as i32;
+ 1f32 as f32;
+ false as bool;
+ &1i32 as &i32;
+
+ -1_i32 as i32;
+ - 1_i32 as i32;
+ -1f32 as f32;
+ 1_i32 as i32;
+ 1_f32 as f32;
+
+ // macro version
+ macro_rules! foo {
+ ($a:ident, $b:ident) => {
+ #[allow(unused)]
+ pub fn $a() -> $b {
+ 1 as $b
+ }
+ };
+ }
+ foo!(a, i32);
+ foo!(b, f32);
+ foo!(c, f64);
+
+ // do not lint cast to cfg-dependant type
+ 1 as std::os::raw::c_char;
+
+ // do not lint cast to alias type
+ 1 as I32Alias;
+ &1 as &I32Alias;
+}
+
+type I32Alias = i32;
+
+mod fixable {
+ #![allow(dead_code)]
+
+ fn main() {
+ // casting integer literal to float is unnecessary
+ 100 as f32;
+ 100 as f64;
+ 100_i32 as f64;
+ let _ = -100 as f32;
+ let _ = -100 as f64;
+ let _ = -100_i32 as f64;
+ 100. as f32;
+ 100. as f64;
+ // Should not trigger
+ #[rustfmt::skip]
+ let v = vec!(1);
+ &v as &[i32];
+ 0x10 as f32;
+ 0o10 as f32;
+ 0b10 as f32;
+ 0x11 as f64;
+ 0o11 as f64;
+ 0b11 as f64;
+
+ 1 as u32;
+ 0x10 as i32;
+ 0b10 as usize;
+ 0o73 as u16;
+ 1_000_000_000 as u32;
+
+ 1.0 as f64;
+ 0.5 as f32;
+
+ 1.0 as u16;
+
+ let _ = -1 as i32;
+ let _ = -1.0 as f32;
+
+ let _ = 1 as I32Alias;
+ let _ = &1 as &I32Alias;
+ }
+
+ type I32Alias = i32;
+
+ fn issue_9380() {
+ let _: i32 = -(1) as i32;
+ let _: f32 = -(1) as f32;
+ let _: i64 = -(1) as i64;
+ let _: i64 = -(1.0) as i64;
+
+ let _ = -(1 + 1) as i64;
+ }
++
++ fn issue_9563() {
++ let _: f64 = (-8.0 as f64).exp();
++ #[allow(clippy::precedence)]
++ let _: f64 = -(8.0 as f64).exp(); // should suggest `-8.0_f64.exp()` here not to change code behavior
++ }
++
++ fn issue_9562_non_literal() {
++ fn foo() -> f32 {
++ 0.
++ }
++
++ let _num = foo() as f32;
++ }
+}
--- /dev/null
- error: aborting due to 27 previous errors
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:14:5
+ |
+LL | 1i32 as i32;
+ | ^^^^^^^^^^^ help: try: `1_i32`
+ |
+ = note: `-D clippy::unnecessary-cast` implied by `-D warnings`
+
+error: casting float literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:15:5
+ |
+LL | 1f32 as f32;
+ | ^^^^^^^^^^^ help: try: `1_f32`
+
+error: casting to the same type is unnecessary (`bool` -> `bool`)
+ --> $DIR/unnecessary_cast.rs:16:5
+ |
+LL | false as bool;
+ | ^^^^^^^^^^^^^ help: try: `false`
+
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:19:5
+ |
+LL | -1_i32 as i32;
+ | ^^^^^^^^^^^^^ help: try: `-1_i32`
+
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:20:5
+ |
+LL | - 1_i32 as i32;
+ | ^^^^^^^^^^^^^^ help: try: `- 1_i32`
+
+error: casting float literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:21:5
+ |
+LL | -1f32 as f32;
+ | ^^^^^^^^^^^^ help: try: `-1_f32`
+
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:22:5
+ |
+LL | 1_i32 as i32;
+ | ^^^^^^^^^^^^ help: try: `1_i32`
+
+error: casting float literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:23:5
+ |
+LL | 1_f32 as f32;
+ | ^^^^^^^^^^^^ help: try: `1_f32`
+
+error: casting integer literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:53:9
+ |
+LL | 100 as f32;
+ | ^^^^^^^^^^ help: try: `100_f32`
+
+error: casting integer literal to `f64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:54:9
+ |
+LL | 100 as f64;
+ | ^^^^^^^^^^ help: try: `100_f64`
+
+error: casting integer literal to `f64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:55:9
+ |
+LL | 100_i32 as f64;
+ | ^^^^^^^^^^^^^^ help: try: `100_f64`
+
+error: casting integer literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:56:17
+ |
+LL | let _ = -100 as f32;
+ | ^^^^^^^^^^^ help: try: `-100_f32`
+
+error: casting integer literal to `f64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:57:17
+ |
+LL | let _ = -100 as f64;
+ | ^^^^^^^^^^^ help: try: `-100_f64`
+
+error: casting integer literal to `f64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:58:17
+ |
+LL | let _ = -100_i32 as f64;
+ | ^^^^^^^^^^^^^^^ help: try: `-100_f64`
+
+error: casting float literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:59:9
+ |
+LL | 100. as f32;
+ | ^^^^^^^^^^^ help: try: `100_f32`
+
+error: casting float literal to `f64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:60:9
+ |
+LL | 100. as f64;
+ | ^^^^^^^^^^^ help: try: `100_f64`
+
+error: casting integer literal to `u32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:72:9
+ |
+LL | 1 as u32;
+ | ^^^^^^^^ help: try: `1_u32`
+
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:73:9
+ |
+LL | 0x10 as i32;
+ | ^^^^^^^^^^^ help: try: `0x10_i32`
+
+error: casting integer literal to `usize` is unnecessary
+ --> $DIR/unnecessary_cast.rs:74:9
+ |
+LL | 0b10 as usize;
+ | ^^^^^^^^^^^^^ help: try: `0b10_usize`
+
+error: casting integer literal to `u16` is unnecessary
+ --> $DIR/unnecessary_cast.rs:75:9
+ |
+LL | 0o73 as u16;
+ | ^^^^^^^^^^^ help: try: `0o73_u16`
+
+error: casting integer literal to `u32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:76:9
+ |
+LL | 1_000_000_000 as u32;
+ | ^^^^^^^^^^^^^^^^^^^^ help: try: `1_000_000_000_u32`
+
+error: casting float literal to `f64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:78:9
+ |
+LL | 1.0 as f64;
+ | ^^^^^^^^^^ help: try: `1.0_f64`
+
+error: casting float literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:79:9
+ |
+LL | 0.5 as f32;
+ | ^^^^^^^^^^ help: try: `0.5_f32`
+
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:83:17
+ |
+LL | let _ = -1 as i32;
+ | ^^^^^^^^^ help: try: `-1_i32`
+
+error: casting float literal to `f32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:84:17
+ |
+LL | let _ = -1.0 as f32;
+ | ^^^^^^^^^^^ help: try: `-1.0_f32`
+
+error: casting integer literal to `i32` is unnecessary
+ --> $DIR/unnecessary_cast.rs:93:22
+ |
+LL | let _: i32 = -(1) as i32;
+ | ^^^^^^^^^^^ help: try: `-1_i32`
+
+error: casting integer literal to `i64` is unnecessary
+ --> $DIR/unnecessary_cast.rs:95:22
+ |
+LL | let _: i64 = -(1) as i64;
+ | ^^^^^^^^^^^ help: try: `-1_i64`
+
++error: casting float literal to `f64` is unnecessary
++ --> $DIR/unnecessary_cast.rs:102:22
++ |
++LL | let _: f64 = (-8.0 as f64).exp();
++ | ^^^^^^^^^^^^^ help: try: `(-8.0_f64)`
++
++error: casting float literal to `f64` is unnecessary
++ --> $DIR/unnecessary_cast.rs:104:23
++ |
++LL | let _: f64 = -(8.0 as f64).exp(); // should suggest `-8.0_f64.exp()` here not to change code behavior
++ | ^^^^^^^^^^^^ help: try: `8.0_f64`
++
++error: casting to the same type is unnecessary (`f32` -> `f32`)
++ --> $DIR/unnecessary_cast.rs:112:20
++ |
++LL | let _num = foo() as f32;
++ | ^^^^^^^^^^^^ help: try: `foo()`
++
++error: aborting due to 30 previous errors
+
--- /dev/null
-
+// does not test any rustfixable lints
- #![allow(unused, clippy::redundant_clone, clippy::unnecessary_wraps)]
+#![warn(clippy::clone_on_ref_ptr)]
++#![allow(unused)]
++#![allow(clippy::redundant_clone, clippy::uninlined_format_args, clippy::unnecessary_wraps)]
+
+use std::cell::RefCell;
+use std::rc::{self, Rc};
+use std::sync::{self, Arc};
+
+trait SomeTrait {}
+struct SomeImpl;
+impl SomeTrait for SomeImpl {}
+
+fn main() {}
+
+fn clone_on_ref_ptr() {
+ let rc = Rc::new(true);
+ let arc = Arc::new(true);
+
+ let rcweak = Rc::downgrade(&rc);
+ let arc_weak = Arc::downgrade(&arc);
+
+ rc.clone();
+ Rc::clone(&rc);
+
+ arc.clone();
+ Arc::clone(&arc);
+
+ rcweak.clone();
+ rc::Weak::clone(&rcweak);
+
+ arc_weak.clone();
+ sync::Weak::clone(&arc_weak);
+
+ let x = Arc::new(SomeImpl);
+ let _: Arc<dyn SomeTrait> = x.clone();
+}
+
+fn clone_on_copy_generic<T: Copy>(t: T) {
+ t.clone();
+
+ Some(t).clone();
+}
+
+fn clone_on_double_ref() {
+ let x = vec![1];
+ let y = &&x;
+ let z: &Vec<_> = y.clone();
+
+ println!("{:p} {:p}", *y, z);
+}
+
+mod many_derefs {
+ struct A;
+ struct B;
+ struct C;
+ struct D;
+ #[derive(Copy, Clone)]
+ struct E;
+
+ macro_rules! impl_deref {
+ ($src:ident, $dst:ident) => {
+ impl std::ops::Deref for $src {
+ type Target = $dst;
+ fn deref(&self) -> &Self::Target {
+ &$dst
+ }
+ }
+ };
+ }
+
+ impl_deref!(A, B);
+ impl_deref!(B, C);
+ impl_deref!(C, D);
+ impl std::ops::Deref for D {
+ type Target = &'static E;
+ fn deref(&self) -> &Self::Target {
+ &&E
+ }
+ }
+
+ fn go1() {
+ let a = A;
+ let _: E = a.clone();
+ let _: E = *****a;
+ }
+
+ fn check(mut encoded: &[u8]) {
+ let _ = &mut encoded.clone();
+ let _ = &encoded.clone();
+ }
+}
+
+mod issue2076 {
+ use std::rc::Rc;
+
+ macro_rules! try_opt {
+ ($expr: expr) => {
+ match $expr {
+ Some(value) => value,
+ None => return None,
+ }
+ };
+ }
+
+ fn func() -> Option<Rc<u8>> {
+ let rc = Rc::new(42);
+ Some(try_opt!(Some(rc)).clone())
+ }
+}
--- /dev/null
-
+// run-rustfix
+#![warn(clippy::unnecessary_join)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<String>();
+ println!("{}", output);
+
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<String>();
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<String>>()
+ .join("\n");
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ println!("{}", output);
+}
--- /dev/null
-
+// run-rustfix
+#![warn(clippy::unnecessary_join)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<String>>()
+ .join("");
+ println!("{}", output);
+
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<_>>()
+ .join("");
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<String>>()
+ .join("\n");
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ println!("{}", output);
+}
--- /dev/null
+// run-rustfix
++// aux-build: proc_macro_with_span.rs
+#![warn(clippy::unnecessary_lazy_evaluations)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::map_identity)]
+
++extern crate proc_macro_with_span;
++use proc_macro_with_span::with_span;
++
+struct Deep(Option<usize>);
+
+#[derive(Copy, Clone)]
+struct SomeStruct {
+ some_field: usize,
+}
+
+impl SomeStruct {
+ fn return_some_field(&self) -> usize {
+ self.some_field
+ }
+}
+
+fn some_call<T: Default>() -> T {
+ T::default()
+}
+
++struct Issue9427(i32);
++
++impl Drop for Issue9427 {
++ fn drop(&mut self) {
++ println!("{}", self.0);
++ }
++}
++
+fn main() {
+ let astronomers_pi = 10;
+ let ext_arr: [usize; 1] = [2];
+ let ext_str = SomeStruct { some_field: 10 };
+
+ let mut opt = Some(42);
+ let ext_opt = Some(42);
+ let nested_opt = Some(Some(42));
+ let nested_tuple_opt = Some(Some((42, 43)));
+ let cond = true;
+
+ // Should lint - Option
+ let _ = opt.unwrap_or(2);
+ let _ = opt.unwrap_or(astronomers_pi);
+ let _ = opt.unwrap_or(ext_str.some_field);
+ let _ = opt.unwrap_or_else(|| ext_arr[0]);
+ let _ = opt.and(ext_opt);
+ let _ = opt.or(ext_opt);
+ let _ = opt.or(None);
+ let _ = opt.get_or_insert(2);
+ let _ = opt.ok_or(2);
+ let _ = nested_tuple_opt.unwrap_or(Some((1, 2)));
+ let _ = cond.then_some(astronomers_pi);
+
+ // Cases when unwrap is not called on a simple variable
+ let _ = Some(10).unwrap_or(2);
+ let _ = Some(10).and(ext_opt);
+ let _: Option<usize> = None.or(ext_opt);
+ let _ = None.get_or_insert(2);
+ let _: Result<usize, usize> = None.ok_or(2);
+ let _: Option<usize> = None.or(None);
+
+ let mut deep = Deep(Some(42));
+ let _ = deep.0.unwrap_or(2);
+ let _ = deep.0.and(ext_opt);
+ let _ = deep.0.or(None);
+ let _ = deep.0.get_or_insert(2);
+ let _ = deep.0.ok_or(2);
+
+ // Should not lint - Option
+ let _ = opt.unwrap_or_else(|| ext_str.return_some_field());
+ let _ = nested_opt.unwrap_or_else(|| Some(some_call()));
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((some_call(), some_call())));
+ let _ = opt.or_else(some_call);
+ let _ = opt.or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(some_call);
+ let _ = deep.0.get_or_insert_with(|| some_call());
+ let _ = deep.0.or_else(some_call);
+ let _ = deep.0.or_else(|| some_call());
+ let _ = opt.ok_or_else(|| ext_arr[0]);
+
++ // Should not lint - bool
++ let _ = (0 == 1).then(|| Issue9427(0)); // Issue9427 has a significant drop
++
+ // should not lint, bind_instead_of_map takes priority
+ let _ = Some(10).and_then(|idx| Some(ext_arr[idx]));
+ let _ = Some(10).and_then(|idx| Some(idx));
+
+ // should lint, bind_instead_of_map doesn't apply
+ let _: Option<usize> = None.or(Some(3));
+ let _ = deep.0.or(Some(3));
+ let _ = opt.or(Some(3));
+
+ // Should lint - Result
+ let res: Result<usize, usize> = Err(5);
+ let res2: Result<usize, SomeStruct> = Err(SomeStruct { some_field: 5 });
+
+ let _ = res2.unwrap_or(2);
+ let _ = res2.unwrap_or(astronomers_pi);
+ let _ = res2.unwrap_or(ext_str.some_field);
+
+ // Should not lint - Result
+ let _ = res.unwrap_or_else(|err| err);
+ let _ = res.unwrap_or_else(|err| ext_arr[err]);
+ let _ = res2.unwrap_or_else(|err| err.some_field);
+ let _ = res2.unwrap_or_else(|err| err.return_some_field());
+ let _ = res2.unwrap_or_else(|_| ext_str.return_some_field());
+
+ // should not lint, bind_instead_of_map takes priority
+ let _: Result<usize, usize> = res.and_then(|x| Ok(x));
+ let _: Result<usize, usize> = res.or_else(|err| Err(err));
+
+ let _: Result<usize, usize> = res.and_then(|_| Ok(2));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Err(2));
+ let _: Result<usize, usize> = res.or_else(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Err(ext_str.some_field));
+
+ // should lint, bind_instead_of_map doesn't apply
+ let _: Result<usize, usize> = res.and(Err(2));
+ let _: Result<usize, usize> = res.and(Err(astronomers_pi));
+ let _: Result<usize, usize> = res.and(Err(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or(Ok(2));
+ let _: Result<usize, usize> = res.or(Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.or(Ok(ext_str.some_field));
+ let _: Result<usize, usize> = res.
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ or(Ok(ext_str.some_field));
+
+ // neither bind_instead_of_map nor unnecessary_lazy_eval applies here
+ let _: Result<usize, usize> = res.and_then(|x| Err(x));
+ let _: Result<usize, usize> = res.or_else(|err| Ok(err));
+}
++
++#[allow(unused)]
++fn issue9485() {
++ // should not lint, is in proc macro
++ with_span!(span Some(42).unwrap_or_else(|| 2););
++}
--- /dev/null
+// run-rustfix
++// aux-build: proc_macro_with_span.rs
+#![warn(clippy::unnecessary_lazy_evaluations)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::map_identity)]
+
++extern crate proc_macro_with_span;
++use proc_macro_with_span::with_span;
++
+struct Deep(Option<usize>);
+
+#[derive(Copy, Clone)]
+struct SomeStruct {
+ some_field: usize,
+}
+
+impl SomeStruct {
+ fn return_some_field(&self) -> usize {
+ self.some_field
+ }
+}
+
+fn some_call<T: Default>() -> T {
+ T::default()
+}
+
++struct Issue9427(i32);
++
++impl Drop for Issue9427 {
++ fn drop(&mut self) {
++ println!("{}", self.0);
++ }
++}
++
+fn main() {
+ let astronomers_pi = 10;
+ let ext_arr: [usize; 1] = [2];
+ let ext_str = SomeStruct { some_field: 10 };
+
+ let mut opt = Some(42);
+ let ext_opt = Some(42);
+ let nested_opt = Some(Some(42));
+ let nested_tuple_opt = Some(Some((42, 43)));
+ let cond = true;
+
+ // Should lint - Option
+ let _ = opt.unwrap_or_else(|| 2);
+ let _ = opt.unwrap_or_else(|| astronomers_pi);
+ let _ = opt.unwrap_or_else(|| ext_str.some_field);
+ let _ = opt.unwrap_or_else(|| ext_arr[0]);
+ let _ = opt.and_then(|_| ext_opt);
+ let _ = opt.or_else(|| ext_opt);
+ let _ = opt.or_else(|| None);
+ let _ = opt.get_or_insert_with(|| 2);
+ let _ = opt.ok_or_else(|| 2);
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2)));
+ let _ = cond.then(|| astronomers_pi);
+
+ // Cases when unwrap is not called on a simple variable
+ let _ = Some(10).unwrap_or_else(|| 2);
+ let _ = Some(10).and_then(|_| ext_opt);
+ let _: Option<usize> = None.or_else(|| ext_opt);
+ let _ = None.get_or_insert_with(|| 2);
+ let _: Result<usize, usize> = None.ok_or_else(|| 2);
+ let _: Option<usize> = None.or_else(|| None);
+
+ let mut deep = Deep(Some(42));
+ let _ = deep.0.unwrap_or_else(|| 2);
+ let _ = deep.0.and_then(|_| ext_opt);
+ let _ = deep.0.or_else(|| None);
+ let _ = deep.0.get_or_insert_with(|| 2);
+ let _ = deep.0.ok_or_else(|| 2);
+
+ // Should not lint - Option
+ let _ = opt.unwrap_or_else(|| ext_str.return_some_field());
+ let _ = nested_opt.unwrap_or_else(|| Some(some_call()));
+ let _ = nested_tuple_opt.unwrap_or_else(|| Some((some_call(), some_call())));
+ let _ = opt.or_else(some_call);
+ let _ = opt.or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(|| some_call());
+ let _: Result<usize, usize> = opt.ok_or_else(some_call);
+ let _ = deep.0.get_or_insert_with(|| some_call());
+ let _ = deep.0.or_else(some_call);
+ let _ = deep.0.or_else(|| some_call());
+ let _ = opt.ok_or_else(|| ext_arr[0]);
+
++ // Should not lint - bool
++ let _ = (0 == 1).then(|| Issue9427(0)); // Issue9427 has a significant drop
++
+ // should not lint, bind_instead_of_map takes priority
+ let _ = Some(10).and_then(|idx| Some(ext_arr[idx]));
+ let _ = Some(10).and_then(|idx| Some(idx));
+
+ // should lint, bind_instead_of_map doesn't apply
+ let _: Option<usize> = None.or_else(|| Some(3));
+ let _ = deep.0.or_else(|| Some(3));
+ let _ = opt.or_else(|| Some(3));
+
+ // Should lint - Result
+ let res: Result<usize, usize> = Err(5);
+ let res2: Result<usize, SomeStruct> = Err(SomeStruct { some_field: 5 });
+
+ let _ = res2.unwrap_or_else(|_| 2);
+ let _ = res2.unwrap_or_else(|_| astronomers_pi);
+ let _ = res2.unwrap_or_else(|_| ext_str.some_field);
+
+ // Should not lint - Result
+ let _ = res.unwrap_or_else(|err| err);
+ let _ = res.unwrap_or_else(|err| ext_arr[err]);
+ let _ = res2.unwrap_or_else(|err| err.some_field);
+ let _ = res2.unwrap_or_else(|err| err.return_some_field());
+ let _ = res2.unwrap_or_else(|_| ext_str.return_some_field());
+
+ // should not lint, bind_instead_of_map takes priority
+ let _: Result<usize, usize> = res.and_then(|x| Ok(x));
+ let _: Result<usize, usize> = res.or_else(|err| Err(err));
+
+ let _: Result<usize, usize> = res.and_then(|_| Ok(2));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Ok(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Err(2));
+ let _: Result<usize, usize> = res.or_else(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Err(ext_str.some_field));
+
+ // should lint, bind_instead_of_map doesn't apply
+ let _: Result<usize, usize> = res.and_then(|_| Err(2));
+ let _: Result<usize, usize> = res.and_then(|_| Err(astronomers_pi));
+ let _: Result<usize, usize> = res.and_then(|_| Err(ext_str.some_field));
+
+ let _: Result<usize, usize> = res.or_else(|_| Ok(2));
+ let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
+ let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
+ let _: Result<usize, usize> = res.
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ or_else(|_| Ok(ext_str.some_field));
+
+ // neither bind_instead_of_map nor unnecessary_lazy_eval applies here
+ let _: Result<usize, usize> = res.and_then(|x| Err(x));
+ let _: Result<usize, usize> = res.or_else(|err| Ok(err));
+}
++
++#[allow(unused)]
++fn issue9485() {
++ // should not lint, is in proc macro
++ with_span!(span Some(42).unwrap_or_else(|| 2););
++}
--- /dev/null
- --> $DIR/unnecessary_lazy_eval.rs:36:13
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:37:13
++ --> $DIR/unnecessary_lazy_eval.rs:48:13
+ |
+LL | let _ = opt.unwrap_or_else(|| 2);
+ | ^^^^--------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
+ |
+ = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:38:13
++ --> $DIR/unnecessary_lazy_eval.rs:49:13
+ |
+LL | let _ = opt.unwrap_or_else(|| astronomers_pi);
+ | ^^^^---------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:40:13
++ --> $DIR/unnecessary_lazy_eval.rs:50:13
+ |
+LL | let _ = opt.unwrap_or_else(|| ext_str.some_field);
+ | ^^^^-------------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:41:13
++ --> $DIR/unnecessary_lazy_eval.rs:52:13
+ |
+LL | let _ = opt.and_then(|_| ext_opt);
+ | ^^^^---------------------
+ | |
+ | help: use `and(..)` instead: `and(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:42:13
++ --> $DIR/unnecessary_lazy_eval.rs:53:13
+ |
+LL | let _ = opt.or_else(|| ext_opt);
+ | ^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:43:13
++ --> $DIR/unnecessary_lazy_eval.rs:54:13
+ |
+LL | let _ = opt.or_else(|| None);
+ | ^^^^----------------
+ | |
+ | help: use `or(..)` instead: `or(None)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:44:13
++ --> $DIR/unnecessary_lazy_eval.rs:55:13
+ |
+LL | let _ = opt.get_or_insert_with(|| 2);
+ | ^^^^------------------------
+ | |
+ | help: use `get_or_insert(..)` instead: `get_or_insert(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:45:13
++ --> $DIR/unnecessary_lazy_eval.rs:56:13
+ |
+LL | let _ = opt.ok_or_else(|| 2);
+ | ^^^^----------------
+ | |
+ | help: use `ok_or(..)` instead: `ok_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:46:13
++ --> $DIR/unnecessary_lazy_eval.rs:57:13
+ |
+LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2)));
+ | ^^^^^^^^^^^^^^^^^-------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))`
+
+error: unnecessary closure used with `bool::then`
- --> $DIR/unnecessary_lazy_eval.rs:49:13
++ --> $DIR/unnecessary_lazy_eval.rs:58:13
+ |
+LL | let _ = cond.then(|| astronomers_pi);
+ | ^^^^^-----------------------
+ | |
+ | help: use `then_some(..)` instead: `then_some(astronomers_pi)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:50:13
++ --> $DIR/unnecessary_lazy_eval.rs:61:13
+ |
+LL | let _ = Some(10).unwrap_or_else(|| 2);
+ | ^^^^^^^^^--------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:51:28
++ --> $DIR/unnecessary_lazy_eval.rs:62:13
+ |
+LL | let _ = Some(10).and_then(|_| ext_opt);
+ | ^^^^^^^^^---------------------
+ | |
+ | help: use `and(..)` instead: `and(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:52:13
++ --> $DIR/unnecessary_lazy_eval.rs:63:28
+ |
+LL | let _: Option<usize> = None.or_else(|| ext_opt);
+ | ^^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:53:35
++ --> $DIR/unnecessary_lazy_eval.rs:64:13
+ |
+LL | let _ = None.get_or_insert_with(|| 2);
+ | ^^^^^------------------------
+ | |
+ | help: use `get_or_insert(..)` instead: `get_or_insert(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:54:28
++ --> $DIR/unnecessary_lazy_eval.rs:65:35
+ |
+LL | let _: Result<usize, usize> = None.ok_or_else(|| 2);
+ | ^^^^^----------------
+ | |
+ | help: use `ok_or(..)` instead: `ok_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:57:13
++ --> $DIR/unnecessary_lazy_eval.rs:66:28
+ |
+LL | let _: Option<usize> = None.or_else(|| None);
+ | ^^^^^----------------
+ | |
+ | help: use `or(..)` instead: `or(None)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:58:13
++ --> $DIR/unnecessary_lazy_eval.rs:69:13
+ |
+LL | let _ = deep.0.unwrap_or_else(|| 2);
+ | ^^^^^^^--------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:59:13
++ --> $DIR/unnecessary_lazy_eval.rs:70:13
+ |
+LL | let _ = deep.0.and_then(|_| ext_opt);
+ | ^^^^^^^---------------------
+ | |
+ | help: use `and(..)` instead: `and(ext_opt)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:60:13
++ --> $DIR/unnecessary_lazy_eval.rs:71:13
+ |
+LL | let _ = deep.0.or_else(|| None);
+ | ^^^^^^^----------------
+ | |
+ | help: use `or(..)` instead: `or(None)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:61:13
++ --> $DIR/unnecessary_lazy_eval.rs:72:13
+ |
+LL | let _ = deep.0.get_or_insert_with(|| 2);
+ | ^^^^^^^------------------------
+ | |
+ | help: use `get_or_insert(..)` instead: `get_or_insert(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:81:28
++ --> $DIR/unnecessary_lazy_eval.rs:73:13
+ |
+LL | let _ = deep.0.ok_or_else(|| 2);
+ | ^^^^^^^----------------
+ | |
+ | help: use `ok_or(..)` instead: `ok_or(2)`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:82:13
++ --> $DIR/unnecessary_lazy_eval.rs:96:28
+ |
+LL | let _: Option<usize> = None.or_else(|| Some(3));
+ | ^^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(Some(3))`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:83:13
++ --> $DIR/unnecessary_lazy_eval.rs:97:13
+ |
+LL | let _ = deep.0.or_else(|| Some(3));
+ | ^^^^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(Some(3))`
+
+error: unnecessary closure used to substitute value for `Option::None`
- --> $DIR/unnecessary_lazy_eval.rs:89:13
++ --> $DIR/unnecessary_lazy_eval.rs:98:13
+ |
+LL | let _ = opt.or_else(|| Some(3));
+ | ^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(Some(3))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:90:13
++ --> $DIR/unnecessary_lazy_eval.rs:104:13
+ |
+LL | let _ = res2.unwrap_or_else(|_| 2);
+ | ^^^^^---------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:91:13
++ --> $DIR/unnecessary_lazy_eval.rs:105:13
+ |
+LL | let _ = res2.unwrap_or_else(|_| astronomers_pi);
+ | ^^^^^----------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:113:35
++ --> $DIR/unnecessary_lazy_eval.rs:106:13
+ |
+LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field);
+ | ^^^^^--------------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:114:35
++ --> $DIR/unnecessary_lazy_eval.rs:128:35
+ |
+LL | let _: Result<usize, usize> = res.and_then(|_| Err(2));
+ | ^^^^--------------------
+ | |
+ | help: use `and(..)` instead: `and(Err(2))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:115:35
++ --> $DIR/unnecessary_lazy_eval.rs:129:35
+ |
+LL | let _: Result<usize, usize> = res.and_then(|_| Err(astronomers_pi));
+ | ^^^^---------------------------------
+ | |
+ | help: use `and(..)` instead: `and(Err(astronomers_pi))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:117:35
++ --> $DIR/unnecessary_lazy_eval.rs:130:35
+ |
+LL | let _: Result<usize, usize> = res.and_then(|_| Err(ext_str.some_field));
+ | ^^^^-------------------------------------
+ | |
+ | help: use `and(..)` instead: `and(Err(ext_str.some_field))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:118:35
++ --> $DIR/unnecessary_lazy_eval.rs:132:35
+ |
+LL | let _: Result<usize, usize> = res.or_else(|_| Ok(2));
+ | ^^^^------------------
+ | |
+ | help: use `or(..)` instead: `or(Ok(2))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:119:35
++ --> $DIR/unnecessary_lazy_eval.rs:133:35
+ |
+LL | let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
+ | ^^^^-------------------------------
+ | |
+ | help: use `or(..)` instead: `or(Ok(astronomers_pi))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
- --> $DIR/unnecessary_lazy_eval.rs:120:35
++ --> $DIR/unnecessary_lazy_eval.rs:134:35
+ |
+LL | let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
+ | ^^^^-----------------------------------
+ | |
+ | help: use `or(..)` instead: `or(Ok(ext_str.some_field))`
+
+error: unnecessary closure used to substitute value for `Result::Err`
++ --> $DIR/unnecessary_lazy_eval.rs:135:35
+ |
+LL | let _: Result<usize, usize> = res.
+ | ___________________________________^
+LL | | // some lines
+LL | | // some lines
+LL | | // some lines
+... |
+LL | | // some lines
+LL | | or_else(|_| Ok(ext_str.some_field));
+ | |_________----------------------------------^
+ | |
+ | help: use `or(..)` instead: `or(Ok(ext_str.some_field))`
+
+error: aborting due to 34 previous errors
+
--- /dev/null
+#![warn(clippy::upper_case_acronyms)]
+
+struct HTTPResponse; // not linted by default, but with cfg option
+
+struct CString; // not linted
+
+enum Flags {
+ NS, // not linted
+ CWR,
+ ECE,
+ URG,
+ ACK,
+ PSH,
+ RST,
+ SYN,
+ FIN,
+}
+
+// linted with cfg option, beware that lint suggests `GccllvmSomething` instead of
+// `GccLlvmSomething`
+struct GCCLLVMSomething;
+
+// public items must not be linted
+pub struct NOWARNINGHERE;
+pub struct ALSONoWarningHERE;
+
+// enum variants should not be linted if the num is pub
+pub enum ParseError<T> {
+ YDB(u8),
+ Utf8(std::string::FromUtf8Error),
+ Parse(T, String),
+}
+
+// private, do lint here
+enum ParseErrorPrivate<T> {
+ WASD(u8),
+ Utf8(std::string::FromUtf8Error),
+ Parse(T, String),
+}
+
++// do lint here
++struct JSON;
++
++// do lint here
++enum YAML {
++ Num(u32),
++ Str(String),
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 9 previous errors
+error: name `CWR` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:9:5
+ |
+LL | CWR,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Cwr`
+ |
+ = note: `-D clippy::upper-case-acronyms` implied by `-D warnings`
+
+error: name `ECE` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:10:5
+ |
+LL | ECE,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Ece`
+
+error: name `URG` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:11:5
+ |
+LL | URG,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Urg`
+
+error: name `ACK` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:12:5
+ |
+LL | ACK,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter (notice the capitalization): `Ack`
+
+error: name `PSH` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:13:5
+ |
+LL | PSH,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Psh`
+
+error: name `RST` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:14:5
+ |
+LL | RST,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Rst`
+
+error: name `SYN` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:15:5
+ |
+LL | SYN,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Syn`
+
+error: name `FIN` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:16:5
+ |
+LL | FIN,
+ | ^^^ help: consider making the acronym lowercase, except the initial letter: `Fin`
+
+error: name `WASD` contains a capitalized acronym
+ --> $DIR/upper_case_acronyms.rs:36:5
+ |
+LL | WASD(u8),
+ | ^^^^ help: consider making the acronym lowercase, except the initial letter: `Wasd`
+
++error: name `JSON` contains a capitalized acronym
++ --> $DIR/upper_case_acronyms.rs:42:8
++ |
++LL | struct JSON;
++ | ^^^^ help: consider making the acronym lowercase, except the initial letter: `Json`
++
++error: name `YAML` contains a capitalized acronym
++ --> $DIR/upper_case_acronyms.rs:45:6
++ |
++LL | enum YAML {
++ | ^^^^ help: consider making the acronym lowercase, except the initial letter: `Yaml`
++
++error: aborting due to 11 previous errors
+
--- /dev/null
-
+// aux-build:proc_macro_derive.rs
- #![allow(clippy::disallowed_names, clippy::eq_op)]
+#![feature(rustc_private)]
+#![warn(clippy::all)]
+#![warn(clippy::used_underscore_binding)]
++#![allow(clippy::disallowed_names, clippy::eq_op, clippy::uninlined_format_args)]
+
+#[macro_use]
+extern crate proc_macro_derive;
+
+// This should not trigger the lint. There's underscore binding inside the external derive that
+// would trigger the `used_underscore_binding` lint.
+#[derive(DeriveSomething)]
+struct Baz;
+
+macro_rules! test_macro {
+ () => {{
+ let _foo = 42;
+ _foo + 1
+ }};
+}
+
+/// Tests that we lint if we use a binding with a single leading underscore
+fn prefix_underscore(_foo: u32) -> u32 {
+ _foo + 1
+}
+
+/// Tests that we lint if we use a `_`-variable defined outside within a macro expansion
+fn in_macro_or_desugar(_foo: u32) {
+ println!("{}", _foo);
+ assert_eq!(_foo, _foo);
+
+ test_macro!() + 1;
+}
+
+// Struct for testing use of fields prefixed with an underscore
+struct StructFieldTest {
+ _underscore_field: u32,
+}
+
+/// Tests that we lint the use of a struct field which is prefixed with an underscore
+fn in_struct_field() {
+ let mut s = StructFieldTest { _underscore_field: 0 };
+ s._underscore_field += 1;
+}
+
+/// Tests that we do not lint if the struct field is used in code created with derive.
+#[derive(Clone, Debug)]
+pub struct UnderscoreInStruct {
+ _foo: u32,
+}
+
+/// Tests that we do not lint if the underscore is not a prefix
+fn non_prefix_underscore(some_foo: u32) -> u32 {
+ some_foo + 1
+}
+
+/// Tests that we do not lint if we do not use the binding (simple case)
+fn unused_underscore_simple(_foo: u32) -> u32 {
+ 1
+}
+
+/// Tests that we do not lint if we do not use the binding (complex case). This checks for
+/// compatibility with the built-in `unused_variables` lint.
+fn unused_underscore_complex(mut _foo: u32) -> u32 {
+ _foo += 1;
+ _foo = 2;
+ 1
+}
+
+/// Test that we do not lint for multiple underscores
+fn multiple_underscores(__foo: u32) -> u32 {
+ __foo + 1
+}
+
+// Non-variable bindings with preceding underscore
+fn _fn_test() {}
+struct _StructTest;
+enum _EnumTest {
+ _Empty,
+ _Value(_StructTest),
+}
+
+/// Tests that we do not lint for non-variable bindings
+fn non_variables() {
+ _fn_test();
+ let _s = _StructTest;
+ let _e = match _EnumTest::_Value(_StructTest) {
+ _EnumTest::_Empty => 0,
+ _EnumTest::_Value(_st) => 1,
+ };
+ let f = _fn_test;
+ f();
+}
+
+// Tests that we do not lint if the binding comes from await desugaring,
+// but we do lint the awaited expression. See issue 5360.
+async fn await_desugaring() {
+ async fn foo() {}
+ fn uses_i(_i: i32) {}
+
+ foo().await;
+ ({
+ let _i = 5;
+ uses_i(_i);
+ foo()
+ })
+ .await
+}
+
+fn main() {
+ let foo = 0u32;
+ // tests of unused_underscore lint
+ let _ = prefix_underscore(foo);
+ in_macro_or_desugar(foo);
+ in_struct_field();
+ // possible false positives
+ let _ = non_prefix_underscore(foo);
+ let _ = unused_underscore_simple(foo);
+ let _ = unused_underscore_complex(foo);
+ let _ = multiple_underscores(foo);
+ non_variables();
+ await_desugaring();
+}
--- /dev/null
- --> $DIR/used_underscore_binding.rs:25:5
+error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
- --> $DIR/used_underscore_binding.rs:30:20
++ --> $DIR/used_underscore_binding.rs:24:5
+ |
+LL | _foo + 1
+ | ^^^^
+ |
+ = note: `-D clippy::used-underscore-binding` implied by `-D warnings`
+
+error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
- --> $DIR/used_underscore_binding.rs:31:16
++ --> $DIR/used_underscore_binding.rs:29:20
+ |
+LL | println!("{}", _foo);
+ | ^^^^
+
+error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
- --> $DIR/used_underscore_binding.rs:31:22
++ --> $DIR/used_underscore_binding.rs:30:16
+ |
+LL | assert_eq!(_foo, _foo);
+ | ^^^^
+
+error: used binding `_foo` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
- --> $DIR/used_underscore_binding.rs:44:5
++ --> $DIR/used_underscore_binding.rs:30:22
+ |
+LL | assert_eq!(_foo, _foo);
+ | ^^^^
+
+error: used binding `_underscore_field` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
- --> $DIR/used_underscore_binding.rs:105:16
++ --> $DIR/used_underscore_binding.rs:43:5
+ |
+LL | s._underscore_field += 1;
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: used binding `_i` which is prefixed with an underscore. A leading underscore signals that a binding will not be used
++ --> $DIR/used_underscore_binding.rs:104:16
+ |
+LL | uses_i(_i);
+ | ^^
+
+error: aborting due to 6 previous errors
+
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::explicit_auto_deref)]
+#![deny(clippy::useless_asref)]
++#![allow(clippy::explicit_auto_deref, clippy::uninlined_format_args)]
+
+use std::fmt::Debug;
+
+struct FakeAsRef;
+
+#[allow(clippy::should_implement_trait)]
+impl FakeAsRef {
+ fn as_ref(&self) -> &Self {
+ self
+ }
+}
+
+struct MoreRef;
+
+impl<'a, 'b, 'c> AsRef<&'a &'b &'c MoreRef> for MoreRef {
+ fn as_ref(&self) -> &&'a &'b &'c MoreRef {
+ &&&&MoreRef
+ }
+}
+
+fn foo_rstr(x: &str) {
+ println!("{:?}", x);
+}
+fn foo_rslice(x: &[i32]) {
+ println!("{:?}", x);
+}
+fn foo_mrslice(x: &mut [i32]) {
+ println!("{:?}", x);
+}
+fn foo_rrrrmr(_: &&&&MoreRef) {
+ println!("so many refs");
+}
+
+fn not_ok() {
+ let rstr: &str = "hello";
+ let mut mrslice: &mut [i32] = &mut [1, 2, 3];
+
+ {
+ let rslice: &[i32] = &*mrslice;
+ foo_rstr(rstr);
+ foo_rstr(rstr);
+ foo_rslice(rslice);
+ foo_rslice(rslice);
+ }
+ {
+ foo_mrslice(mrslice);
+ foo_mrslice(mrslice);
+ foo_rslice(mrslice);
+ foo_rslice(mrslice);
+ }
+
+ {
+ let rrrrrstr = &&&&rstr;
+ let rrrrrslice = &&&&&*mrslice;
+ foo_rslice(rrrrrslice);
+ foo_rslice(rrrrrslice);
+ foo_rstr(rrrrrstr);
+ foo_rstr(rrrrrstr);
+ }
+ {
+ let mrrrrrslice = &mut &mut &mut &mut mrslice;
+ foo_mrslice(mrrrrrslice);
+ foo_mrslice(mrrrrrslice);
+ foo_rslice(mrrrrrslice);
+ foo_rslice(mrrrrrslice);
+ }
+ #[allow(unused_parens, clippy::double_parens, clippy::needless_borrow)]
+ foo_rrrrmr((&&&&MoreRef));
+
+ generic_not_ok(mrslice);
+ generic_ok(mrslice);
+}
+
+fn ok() {
+ let string = "hello".to_owned();
+ let mut arr = [1, 2, 3];
+ let mut vec = vec![1, 2, 3];
+
+ {
+ foo_rstr(string.as_ref());
+ foo_rslice(arr.as_ref());
+ foo_rslice(vec.as_ref());
+ }
+ {
+ foo_mrslice(arr.as_mut());
+ foo_mrslice(vec.as_mut());
+ }
+
+ {
+ let rrrrstring = &&&&string;
+ let rrrrarr = &&&&arr;
+ let rrrrvec = &&&&vec;
+ foo_rstr(rrrrstring.as_ref());
+ foo_rslice(rrrrarr.as_ref());
+ foo_rslice(rrrrvec.as_ref());
+ }
+ {
+ let mrrrrarr = &mut &mut &mut &mut arr;
+ let mrrrrvec = &mut &mut &mut &mut vec;
+ foo_mrslice(mrrrrarr.as_mut());
+ foo_mrslice(mrrrrvec.as_mut());
+ }
+ FakeAsRef.as_ref();
+ foo_rrrrmr(MoreRef.as_ref());
+
+ generic_not_ok(arr.as_mut());
+ generic_ok(&mut arr);
+}
+
+fn foo_mrt<T: Debug + ?Sized>(t: &mut T) {
+ println!("{:?}", t);
+}
+fn foo_rt<T: Debug + ?Sized>(t: &T) {
+ println!("{:?}", t);
+}
+
+fn generic_not_ok<T: AsMut<T> + AsRef<T> + Debug + ?Sized>(mrt: &mut T) {
+ foo_mrt(mrt);
+ foo_mrt(mrt);
+ foo_rt(mrt);
+ foo_rt(mrt);
+}
+
+fn generic_ok<U: AsMut<T> + AsRef<T> + ?Sized, T: Debug + ?Sized>(mru: &mut U) {
+ foo_mrt(mru.as_mut());
+ foo_rt(mru.as_ref());
+}
+
+fn main() {
+ not_ok();
+ ok();
+}
--- /dev/null
-
+// run-rustfix
- #![allow(clippy::explicit_auto_deref)]
+#![deny(clippy::useless_asref)]
++#![allow(clippy::explicit_auto_deref, clippy::uninlined_format_args)]
+
+use std::fmt::Debug;
+
+struct FakeAsRef;
+
+#[allow(clippy::should_implement_trait)]
+impl FakeAsRef {
+ fn as_ref(&self) -> &Self {
+ self
+ }
+}
+
+struct MoreRef;
+
+impl<'a, 'b, 'c> AsRef<&'a &'b &'c MoreRef> for MoreRef {
+ fn as_ref(&self) -> &&'a &'b &'c MoreRef {
+ &&&&MoreRef
+ }
+}
+
+fn foo_rstr(x: &str) {
+ println!("{:?}", x);
+}
+fn foo_rslice(x: &[i32]) {
+ println!("{:?}", x);
+}
+fn foo_mrslice(x: &mut [i32]) {
+ println!("{:?}", x);
+}
+fn foo_rrrrmr(_: &&&&MoreRef) {
+ println!("so many refs");
+}
+
+fn not_ok() {
+ let rstr: &str = "hello";
+ let mut mrslice: &mut [i32] = &mut [1, 2, 3];
+
+ {
+ let rslice: &[i32] = &*mrslice;
+ foo_rstr(rstr.as_ref());
+ foo_rstr(rstr);
+ foo_rslice(rslice.as_ref());
+ foo_rslice(rslice);
+ }
+ {
+ foo_mrslice(mrslice.as_mut());
+ foo_mrslice(mrslice);
+ foo_rslice(mrslice.as_ref());
+ foo_rslice(mrslice);
+ }
+
+ {
+ let rrrrrstr = &&&&rstr;
+ let rrrrrslice = &&&&&*mrslice;
+ foo_rslice(rrrrrslice.as_ref());
+ foo_rslice(rrrrrslice);
+ foo_rstr(rrrrrstr.as_ref());
+ foo_rstr(rrrrrstr);
+ }
+ {
+ let mrrrrrslice = &mut &mut &mut &mut mrslice;
+ foo_mrslice(mrrrrrslice.as_mut());
+ foo_mrslice(mrrrrrslice);
+ foo_rslice(mrrrrrslice.as_ref());
+ foo_rslice(mrrrrrslice);
+ }
+ #[allow(unused_parens, clippy::double_parens, clippy::needless_borrow)]
+ foo_rrrrmr((&&&&MoreRef).as_ref());
+
+ generic_not_ok(mrslice);
+ generic_ok(mrslice);
+}
+
+fn ok() {
+ let string = "hello".to_owned();
+ let mut arr = [1, 2, 3];
+ let mut vec = vec![1, 2, 3];
+
+ {
+ foo_rstr(string.as_ref());
+ foo_rslice(arr.as_ref());
+ foo_rslice(vec.as_ref());
+ }
+ {
+ foo_mrslice(arr.as_mut());
+ foo_mrslice(vec.as_mut());
+ }
+
+ {
+ let rrrrstring = &&&&string;
+ let rrrrarr = &&&&arr;
+ let rrrrvec = &&&&vec;
+ foo_rstr(rrrrstring.as_ref());
+ foo_rslice(rrrrarr.as_ref());
+ foo_rslice(rrrrvec.as_ref());
+ }
+ {
+ let mrrrrarr = &mut &mut &mut &mut arr;
+ let mrrrrvec = &mut &mut &mut &mut vec;
+ foo_mrslice(mrrrrarr.as_mut());
+ foo_mrslice(mrrrrvec.as_mut());
+ }
+ FakeAsRef.as_ref();
+ foo_rrrrmr(MoreRef.as_ref());
+
+ generic_not_ok(arr.as_mut());
+ generic_ok(&mut arr);
+}
+
+fn foo_mrt<T: Debug + ?Sized>(t: &mut T) {
+ println!("{:?}", t);
+}
+fn foo_rt<T: Debug + ?Sized>(t: &T) {
+ println!("{:?}", t);
+}
+
+fn generic_not_ok<T: AsMut<T> + AsRef<T> + Debug + ?Sized>(mrt: &mut T) {
+ foo_mrt(mrt.as_mut());
+ foo_mrt(mrt);
+ foo_rt(mrt.as_ref());
+ foo_rt(mrt);
+}
+
+fn generic_ok<U: AsMut<T> + AsRef<T> + ?Sized, T: Debug + ?Sized>(mru: &mut U) {
+ foo_mrt(mru.as_mut());
+ foo_rt(mru.as_ref());
+}
+
+fn main() {
+ not_ok();
+ ok();
+}
--- /dev/null
- --> $DIR/useless_asref.rs:44:18
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:3:9
++ --> $DIR/useless_asref.rs:43:18
+ |
+LL | foo_rstr(rstr.as_ref());
+ | ^^^^^^^^^^^^^ help: try this: `rstr`
+ |
+note: the lint level is defined here
- --> $DIR/useless_asref.rs:46:20
++ --> $DIR/useless_asref.rs:2:9
+ |
+LL | #![deny(clippy::useless_asref)]
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:50:21
++ --> $DIR/useless_asref.rs:45:20
+ |
+LL | foo_rslice(rslice.as_ref());
+ | ^^^^^^^^^^^^^^^ help: try this: `rslice`
+
+error: this call to `as_mut` does nothing
- --> $DIR/useless_asref.rs:52:20
++ --> $DIR/useless_asref.rs:49:21
+ |
+LL | foo_mrslice(mrslice.as_mut());
+ | ^^^^^^^^^^^^^^^^ help: try this: `mrslice`
+
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:59:20
++ --> $DIR/useless_asref.rs:51:20
+ |
+LL | foo_rslice(mrslice.as_ref());
+ | ^^^^^^^^^^^^^^^^ help: try this: `mrslice`
+
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:61:18
++ --> $DIR/useless_asref.rs:58:20
+ |
+LL | foo_rslice(rrrrrslice.as_ref());
+ | ^^^^^^^^^^^^^^^^^^^ help: try this: `rrrrrslice`
+
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:66:21
++ --> $DIR/useless_asref.rs:60:18
+ |
+LL | foo_rstr(rrrrrstr.as_ref());
+ | ^^^^^^^^^^^^^^^^^ help: try this: `rrrrrstr`
+
+error: this call to `as_mut` does nothing
- --> $DIR/useless_asref.rs:68:20
++ --> $DIR/useless_asref.rs:65:21
+ |
+LL | foo_mrslice(mrrrrrslice.as_mut());
+ | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice`
+
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:72:16
++ --> $DIR/useless_asref.rs:67:20
+ |
+LL | foo_rslice(mrrrrrslice.as_ref());
+ | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice`
+
+error: this call to `as_ref` does nothing
- --> $DIR/useless_asref.rs:122:13
++ --> $DIR/useless_asref.rs:71:16
+ |
+LL | foo_rrrrmr((&&&&MoreRef).as_ref());
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(&&&&MoreRef)`
+
+error: this call to `as_mut` does nothing
- --> $DIR/useless_asref.rs:124:12
++ --> $DIR/useless_asref.rs:121:13
+ |
+LL | foo_mrt(mrt.as_mut());
+ | ^^^^^^^^^^^^ help: try this: `mrt`
+
+error: this call to `as_ref` does nothing
++ --> $DIR/useless_asref.rs:123:12
+ |
+LL | foo_rt(mrt.as_ref());
+ | ^^^^^^^^^^^^ help: try this: `mrt`
+
+error: aborting due to 11 previous errors
+
--- /dev/null
- #![allow(clippy::nonstandard_macro_braces)]
+// run-rustfix
+#![warn(clippy::useless_vec)]
++#![allow(clippy::nonstandard_macro_braces, clippy::uninlined_format_args)]
+
+#[derive(Debug)]
+struct NonCopy;
+
+fn on_slice(_: &[u8]) {}
+
+fn on_mut_slice(_: &mut [u8]) {}
+
+#[allow(clippy::ptr_arg)]
+fn on_vec(_: &Vec<u8>) {}
+
+fn on_mut_vec(_: &mut Vec<u8>) {}
+
+struct Line {
+ length: usize,
+}
+
+impl Line {
+ fn length(&self) -> usize {
+ self.length
+ }
+}
+
+fn main() {
+ on_slice(&[]);
+ on_slice(&[]);
+ on_mut_slice(&mut []);
+
+ on_slice(&[1, 2]);
+ on_slice(&[1, 2]);
+ on_mut_slice(&mut [1, 2]);
+
+ on_slice(&[1, 2]);
+ on_slice(&[1, 2]);
+ on_mut_slice(&mut [1, 2]);
+ #[rustfmt::skip]
+ on_slice(&[1, 2]);
+ on_slice(&[1, 2]);
+ on_mut_slice(&mut [1, 2]);
+
+ on_slice(&[1; 2]);
+ on_slice(&[1; 2]);
+ on_mut_slice(&mut [1; 2]);
+
+ on_vec(&vec![]);
+ on_vec(&vec![1, 2]);
+ on_vec(&vec![1; 2]);
+ on_mut_vec(&mut vec![]);
+ on_mut_vec(&mut vec![1, 2]);
+ on_mut_vec(&mut vec![1; 2]);
+
+ // Now with non-constant expressions
+ let line = Line { length: 2 };
+
+ on_slice(&vec![2; line.length]);
+ on_slice(&vec![2; line.length()]);
+ on_mut_slice(&mut vec![2; line.length]);
+ on_mut_slice(&mut vec![2; line.length()]);
+
+ for a in &[1, 2, 3] {
+ println!("{:?}", a);
+ }
+
+ for a in vec![NonCopy, NonCopy] {
+ println!("{:?}", a);
+ }
+
+ on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack`
+ on_mut_vec(&mut vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack`
+
+ // Ok
+ for a in vec![1; 201] {
+ println!("{:?}", a);
+ }
+}
--- /dev/null
- #![allow(clippy::nonstandard_macro_braces)]
+// run-rustfix
+#![warn(clippy::useless_vec)]
++#![allow(clippy::nonstandard_macro_braces, clippy::uninlined_format_args)]
+
+#[derive(Debug)]
+struct NonCopy;
+
+fn on_slice(_: &[u8]) {}
+
+fn on_mut_slice(_: &mut [u8]) {}
+
+#[allow(clippy::ptr_arg)]
+fn on_vec(_: &Vec<u8>) {}
+
+fn on_mut_vec(_: &mut Vec<u8>) {}
+
+struct Line {
+ length: usize,
+}
+
+impl Line {
+ fn length(&self) -> usize {
+ self.length
+ }
+}
+
+fn main() {
+ on_slice(&vec![]);
+ on_slice(&[]);
+ on_mut_slice(&mut vec![]);
+
+ on_slice(&vec![1, 2]);
+ on_slice(&[1, 2]);
+ on_mut_slice(&mut vec![1, 2]);
+
+ on_slice(&vec![1, 2]);
+ on_slice(&[1, 2]);
+ on_mut_slice(&mut vec![1, 2]);
+ #[rustfmt::skip]
+ on_slice(&vec!(1, 2));
+ on_slice(&[1, 2]);
+ on_mut_slice(&mut vec![1, 2]);
+
+ on_slice(&vec![1; 2]);
+ on_slice(&[1; 2]);
+ on_mut_slice(&mut vec![1; 2]);
+
+ on_vec(&vec![]);
+ on_vec(&vec![1, 2]);
+ on_vec(&vec![1; 2]);
+ on_mut_vec(&mut vec![]);
+ on_mut_vec(&mut vec![1, 2]);
+ on_mut_vec(&mut vec![1; 2]);
+
+ // Now with non-constant expressions
+ let line = Line { length: 2 };
+
+ on_slice(&vec![2; line.length]);
+ on_slice(&vec![2; line.length()]);
+ on_mut_slice(&mut vec![2; line.length]);
+ on_mut_slice(&mut vec![2; line.length()]);
+
+ for a in vec![1, 2, 3] {
+ println!("{:?}", a);
+ }
+
+ for a in vec![NonCopy, NonCopy] {
+ println!("{:?}", a);
+ }
+
+ on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack`
+ on_mut_vec(&mut vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack`
+
+ // Ok
+ for a in vec![1; 201] {
+ println!("{:?}", a);
+ }
+}
--- /dev/null
+#![warn(clippy::while_let_loop)]
++#![allow(clippy::uninlined_format_args)]
+
+fn main() {
+ let y = Some(true);
+ loop {
+ if let Some(_x) = y {
+ let _v = 1;
+ } else {
+ break;
+ }
+ }
+
+ #[allow(clippy::never_loop)]
+ loop {
+ // no error, break is not in else clause
+ if let Some(_x) = y {
+ let _v = 1;
+ }
+ break;
+ }
+
+ loop {
+ match y {
+ Some(_x) => true,
+ None => break,
+ };
+ }
+
+ loop {
+ let x = match y {
+ Some(x) => x,
+ None => break,
+ };
+ let _x = x;
+ let _str = "foo";
+ }
+
+ loop {
+ let x = match y {
+ Some(x) => x,
+ None => break,
+ };
+ {
+ let _a = "bar";
+ };
+ {
+ let _b = "foobar";
+ }
+ }
+
+ loop {
+ // no error, else branch does something other than break
+ match y {
+ Some(_x) => true,
+ _ => {
+ let _z = 1;
+ break;
+ },
+ };
+ }
+
+ while let Some(x) = y {
+ // no error, obviously
+ println!("{}", x);
+ }
+
+ // #675, this used to have a wrong suggestion
+ loop {
+ let (e, l) = match "".split_whitespace().next() {
+ Some(word) => (word.is_empty(), word.len()),
+ None => break,
+ };
+
+ let _ = (e, l);
+ }
+}
+
+fn issue771() {
+ let mut a = 100;
+ let b = Some(true);
+ loop {
+ if a > 10 {
+ break;
+ }
+
+ match b {
+ Some(_) => a = 0,
+ None => break,
+ }
+ }
+}
+
+fn issue1017() {
+ let r: Result<u32, u32> = Ok(42);
+ let mut len = 1337;
+
+ loop {
+ match r {
+ Err(_) => len = 0,
+ Ok(length) => {
+ len = length;
+ break;
+ },
+ }
+ }
+}
+
+#[allow(clippy::never_loop)]
+fn issue1948() {
+ // should not trigger clippy::while_let_loop lint because break passes an expression
+ let a = Some(10);
+ let b = loop {
+ if let Some(c) = a {
+ break Some(c);
+ } else {
+ break None;
+ }
+ };
+}
+
+fn issue_7913(m: &std::sync::Mutex<Vec<u32>>) {
+ // Don't lint. The lock shouldn't be held while printing.
+ loop {
+ let x = if let Some(x) = m.lock().unwrap().pop() {
+ x
+ } else {
+ break;
+ };
+
+ println!("{}", x);
+ }
+}
+
+fn issue_5715(mut m: core::cell::RefCell<Option<u32>>) {
+ // Don't lint. The temporary from `borrow_mut` must be dropped before overwriting the `RefCell`.
+ loop {
+ let x = if let &mut Some(x) = &mut *m.borrow_mut() {
+ x
+ } else {
+ break;
+ };
+
+ m = core::cell::RefCell::new(Some(x + 1));
+ }
+}
--- /dev/null
- --> $DIR/while_let_loop.rs:5:5
+error: this loop could be written as a `while let` loop
- --> $DIR/while_let_loop.rs:22:5
++ --> $DIR/while_let_loop.rs:6:5
+ |
+LL | / loop {
+LL | | if let Some(_x) = y {
+LL | | let _v = 1;
+LL | | } else {
+LL | | break;
+LL | | }
+LL | | }
+ | |_____^ help: try: `while let Some(_x) = y { .. }`
+ |
+ = note: `-D clippy::while-let-loop` implied by `-D warnings`
+
+error: this loop could be written as a `while let` loop
- --> $DIR/while_let_loop.rs:29:5
++ --> $DIR/while_let_loop.rs:23:5
+ |
+LL | / loop {
+LL | | match y {
+LL | | Some(_x) => true,
+LL | | None => break,
+LL | | };
+LL | | }
+ | |_____^ help: try: `while let Some(_x) = y { .. }`
+
+error: this loop could be written as a `while let` loop
- --> $DIR/while_let_loop.rs:38:5
++ --> $DIR/while_let_loop.rs:30:5
+ |
+LL | / loop {
+LL | | let x = match y {
+LL | | Some(x) => x,
+LL | | None => break,
+... |
+LL | | let _str = "foo";
+LL | | }
+ | |_____^ help: try: `while let Some(x) = y { .. }`
+
+error: this loop could be written as a `while let` loop
- --> $DIR/while_let_loop.rs:68:5
++ --> $DIR/while_let_loop.rs:39:5
+ |
+LL | / loop {
+LL | | let x = match y {
+LL | | Some(x) => x,
+LL | | None => break,
+... |
+LL | | }
+LL | | }
+ | |_____^ help: try: `while let Some(x) = y { .. }`
+
+error: this loop could be written as a `while let` loop
++ --> $DIR/while_let_loop.rs:69:5
+ |
+LL | / loop {
+LL | | let (e, l) = match "".split_whitespace().next() {
+LL | | Some(word) => (word.is_empty(), word.len()),
+LL | | None => break,
+... |
+LL | | let _ = (e, l);
+LL | | }
+ | |_____^ help: try: `while let Some(word) = "".split_whitespace().next() { .. }`
+
+error: aborting due to 5 previous errors
+
--- /dev/null
-
+// run-rustfix
- clippy::never_loop,
- unreachable_code,
- unused_mut,
- dead_code,
+#![warn(clippy::while_let_on_iterator)]
++#![allow(dead_code, unreachable_code, unused_mut)]
+#![allow(
- clippy::redundant_closure_call
+ clippy::equatable_if_let,
+ clippy::manual_find,
++ clippy::never_loop,
++ clippy::redundant_closure_call,
++ clippy::uninlined_format_args
+)]
+
+fn base() {
+ let mut iter = 1..20;
+ for x in iter {
+ println!("{}", x);
+ }
+
+ let mut iter = 1..20;
+ for x in iter {
+ println!("{}", x);
+ }
+
+ let mut iter = 1..20;
+ for _ in iter {}
+
+ let mut iter = 1..20;
+ while let None = iter.next() {} // this is fine (if nonsensical)
+
+ let mut iter = 1..20;
+ if let Some(x) = iter.next() {
+ // also fine
+ println!("{}", x)
+ }
+
+ // the following shouldn't warn because it can't be written with a for loop
+ let mut iter = 1u32..20;
+ while let Some(_) = iter.next() {
+ println!("next: {:?}", iter.next())
+ }
+
+ // neither can this
+ let mut iter = 1u32..20;
+ while let Some(_) = iter.next() {
+ println!("next: {:?}", iter.next());
+ }
+
+ // or this
+ let mut iter = 1u32..20;
+ while let Some(_) = iter.next() {
+ iter = 1..20;
+ }
+}
+
+// Issue #1188
+fn refutable() {
+ let a = [42, 1337];
+ let mut b = a.iter();
+
+ // consume all the 42s
+ while let Some(&42) = b.next() {}
+
+ let a = [(1, 2, 3)];
+ let mut b = a.iter();
+
+ while let Some(&(1, 2, 3)) = b.next() {}
+
+ let a = [Some(42)];
+ let mut b = a.iter();
+
+ while let Some(&None) = b.next() {}
+
+ /* This gives “refutable pattern in `for` loop binding: `&_` not covered”
+ for &42 in b {}
+ for &(1, 2, 3) in b {}
+ for &Option::None in b.next() {}
+ // */
+}
+
+fn refutable2() {
+ // Issue 3780
+ {
+ let v = vec![1, 2, 3];
+ let mut it = v.windows(2);
+ while let Some([x, y]) = it.next() {
+ println!("x: {}", x);
+ println!("y: {}", y);
+ }
+
+ let mut it = v.windows(2);
+ while let Some([x, ..]) = it.next() {
+ println!("x: {}", x);
+ }
+
+ let mut it = v.windows(2);
+ while let Some([.., y]) = it.next() {
+ println!("y: {}", y);
+ }
+
+ let mut it = v.windows(2);
+ for [..] in it {}
+
+ let v = vec![[1], [2], [3]];
+ let mut it = v.iter();
+ while let Some([1]) = it.next() {}
+
+ let mut it = v.iter();
+ for [_x] in it {}
+ }
+
+ // binding
+ {
+ let v = vec![1, 2, 3];
+ let mut it = v.iter();
+ while let Some(x @ 1) = it.next() {
+ println!("{}", x);
+ }
+
+ let v = vec![[1], [2], [3]];
+ let mut it = v.iter();
+ for x @ [_] in it {
+ println!("{:?}", x);
+ }
+ }
+
+ // false negative
+ {
+ let v = vec![1, 2, 3];
+ let mut it = v.iter().map(Some);
+ while let Some(Some(_) | None) = it.next() {
+ println!("1");
+ }
+ }
+}
+
+fn nested_loops() {
+ let a = [42, 1337];
+
+ loop {
+ let mut y = a.iter();
+ for _ in y {
+ // use a for loop here
+ }
+ }
+}
+
+fn issue1121() {
+ use std::collections::HashSet;
+ let mut values = HashSet::new();
+ values.insert(1);
+
+ while let Some(&value) = values.iter().next() {
+ values.remove(&value);
+ }
+}
+
+fn issue2965() {
+ // This should not cause an ICE
+
+ use std::collections::HashSet;
+ let mut values = HashSet::new();
+ values.insert(1);
+
+ while let Some(..) = values.iter().next() {}
+}
+
+fn issue3670() {
+ let array = [Some(0), None, Some(1)];
+ let mut iter = array.iter();
+
+ while let Some(elem) = iter.next() {
+ let _ = elem.or_else(|| *iter.next()?);
+ }
+}
+
+fn issue1654() {
+ // should not lint if the iterator is generated on every iteration
+ use std::collections::HashSet;
+ let mut values = HashSet::new();
+ values.insert(1);
+
+ while let Some(..) = values.iter().next() {
+ values.remove(&1);
+ }
+
+ while let Some(..) = values.iter().map(|x| x + 1).next() {}
+
+ let chars = "Hello, World!".char_indices();
+ while let Some((i, ch)) = chars.clone().next() {
+ println!("{}: {}", i, ch);
+ }
+}
+
+fn issue6491() {
+ // Used in outer loop, needs &mut
+ let mut it = 1..40;
+ while let Some(n) = it.next() {
+ for m in it.by_ref() {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+ println!("n still is {}", n);
+ }
+
+ // This is fine, inner loop uses a new iterator.
+ let mut it = 1..40;
+ for n in it {
+ let mut it = 1..40;
+ for m in it {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+
+ // Weird binding shouldn't change anything.
+ let (mut it, _) = (1..40, 0);
+ for m in it {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+
+ // Used after the loop, needs &mut.
+ let mut it = 1..40;
+ for m in it.by_ref() {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+ println!("next item {}", it.next().unwrap());
+
+ println!("n still is {}", n);
+ }
+}
+
+fn issue6231() {
+ // Closure in the outer loop, needs &mut
+ let mut it = 1..40;
+ let mut opt = Some(0);
+ while let Some(n) = opt.take().or_else(|| it.next()) {
+ for m in it.by_ref() {
+ if n % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+ println!("n still is {}", n);
+ }
+}
+
+fn issue1924() {
+ struct S<T>(T);
+ impl<T: Iterator<Item = u32>> S<T> {
+ fn f(&mut self) -> Option<u32> {
+ // Used as a field.
+ for i in self.0.by_ref() {
+ if !(3..8).contains(&i) {
+ return Some(i);
+ }
+ }
+ None
+ }
+
+ fn f2(&mut self) -> Option<u32> {
+ // Don't lint, self borrowed inside the loop
+ while let Some(i) = self.0.next() {
+ if i == 1 {
+ return self.f();
+ }
+ }
+ None
+ }
+ }
+ impl<T: Iterator<Item = u32>> S<(S<T>, Option<u32>)> {
+ fn f3(&mut self) -> Option<u32> {
+ // Don't lint, self borrowed inside the loop
+ while let Some(i) = self.0.0.0.next() {
+ if i == 1 {
+ return self.0.0.f();
+ }
+ }
+ while let Some(i) = self.0.0.0.next() {
+ if i == 1 {
+ return self.f3();
+ }
+ }
+ // This one is fine, a different field is borrowed
+ for i in self.0.0.0.by_ref() {
+ if i == 1 {
+ return self.0.1.take();
+ } else {
+ self.0.1 = Some(i);
+ }
+ }
+ None
+ }
+ }
+
+ struct S2<T>(T, u32);
+ impl<T: Iterator<Item = u32>> Iterator for S2<T> {
+ type Item = u32;
+ fn next(&mut self) -> Option<u32> {
+ self.0.next()
+ }
+ }
+
+ // Don't lint, field of the iterator is accessed in the loop
+ let mut it = S2(1..40, 0);
+ while let Some(n) = it.next() {
+ if n == it.1 {
+ break;
+ }
+ }
+
+ // Needs &mut, field of the iterator is accessed after the loop
+ let mut it = S2(1..40, 0);
+ for n in it.by_ref() {
+ if n == 0 {
+ break;
+ }
+ }
+ println!("iterator field {}", it.1);
+}
+
+fn issue7249() {
+ let mut it = 0..10;
+ let mut x = || {
+ // Needs &mut, the closure can be called multiple times
+ for x in it.by_ref() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ };
+ x();
+ x();
+}
+
+fn issue7510() {
+ let mut it = 0..10;
+ let it = &mut it;
+ // Needs to reborrow `it` as the binding isn't mutable
+ for x in it.by_ref() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ println!("{}", it.next().unwrap());
+
+ struct S<T>(T);
+ let mut it = 0..10;
+ let it = S(&mut it);
+ // Needs to reborrow `it.0` as the binding isn't mutable
+ for x in it.0.by_ref() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ println!("{}", it.0.next().unwrap());
+}
+
+fn exact_match_with_single_field() {
+ struct S<T>(T);
+ let mut s = S(0..10);
+ // Don't lint. `s.0` is used inside the loop.
+ while let Some(_) = s.0.next() {
+ let _ = &mut s.0;
+ }
+}
+
+fn custom_deref() {
+ struct S1<T> {
+ x: T,
+ }
+ struct S2<T>(S1<T>);
+ impl<T> core::ops::Deref for S2<T> {
+ type Target = S1<T>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ impl<T> core::ops::DerefMut for S2<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+ }
+
+ let mut s = S2(S1 { x: 0..10 });
+ for x in s.x.by_ref() {
+ println!("{}", x);
+ }
+}
+
+fn issue_8113() {
+ let mut x = [0..10];
+ for x in x[0].by_ref() {
+ println!("{}", x);
+ }
+}
+
+fn fn_once_closure() {
+ let mut it = 0..10;
+ (|| {
+ for x in it {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ })();
+
+ fn f(_: impl FnOnce()) {}
+ let mut it = 0..10;
+ f(|| {
+ for x in it {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ });
+
+ fn f2(_: impl FnMut()) {}
+ let mut it = 0..10;
+ f2(|| {
+ for x in it.by_ref() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ });
+
+ fn f3(_: fn()) {}
+ f3(|| {
+ let mut it = 0..10;
+ for x in it {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ })
+}
+
+fn main() {
+ let mut it = 0..20;
+ for _ in it {
+ println!("test");
+ }
+}
--- /dev/null
-
+// run-rustfix
- clippy::never_loop,
- unreachable_code,
- unused_mut,
- dead_code,
+#![warn(clippy::while_let_on_iterator)]
++#![allow(dead_code, unreachable_code, unused_mut)]
+#![allow(
- clippy::redundant_closure_call
+ clippy::equatable_if_let,
+ clippy::manual_find,
++ clippy::never_loop,
++ clippy::redundant_closure_call,
++ clippy::uninlined_format_args
+)]
+
+fn base() {
+ let mut iter = 1..20;
+ while let Option::Some(x) = iter.next() {
+ println!("{}", x);
+ }
+
+ let mut iter = 1..20;
+ while let Some(x) = iter.next() {
+ println!("{}", x);
+ }
+
+ let mut iter = 1..20;
+ while let Some(_) = iter.next() {}
+
+ let mut iter = 1..20;
+ while let None = iter.next() {} // this is fine (if nonsensical)
+
+ let mut iter = 1..20;
+ if let Some(x) = iter.next() {
+ // also fine
+ println!("{}", x)
+ }
+
+ // the following shouldn't warn because it can't be written with a for loop
+ let mut iter = 1u32..20;
+ while let Some(_) = iter.next() {
+ println!("next: {:?}", iter.next())
+ }
+
+ // neither can this
+ let mut iter = 1u32..20;
+ while let Some(_) = iter.next() {
+ println!("next: {:?}", iter.next());
+ }
+
+ // or this
+ let mut iter = 1u32..20;
+ while let Some(_) = iter.next() {
+ iter = 1..20;
+ }
+}
+
+// Issue #1188
+fn refutable() {
+ let a = [42, 1337];
+ let mut b = a.iter();
+
+ // consume all the 42s
+ while let Some(&42) = b.next() {}
+
+ let a = [(1, 2, 3)];
+ let mut b = a.iter();
+
+ while let Some(&(1, 2, 3)) = b.next() {}
+
+ let a = [Some(42)];
+ let mut b = a.iter();
+
+ while let Some(&None) = b.next() {}
+
+ /* This gives “refutable pattern in `for` loop binding: `&_` not covered”
+ for &42 in b {}
+ for &(1, 2, 3) in b {}
+ for &Option::None in b.next() {}
+ // */
+}
+
+fn refutable2() {
+ // Issue 3780
+ {
+ let v = vec![1, 2, 3];
+ let mut it = v.windows(2);
+ while let Some([x, y]) = it.next() {
+ println!("x: {}", x);
+ println!("y: {}", y);
+ }
+
+ let mut it = v.windows(2);
+ while let Some([x, ..]) = it.next() {
+ println!("x: {}", x);
+ }
+
+ let mut it = v.windows(2);
+ while let Some([.., y]) = it.next() {
+ println!("y: {}", y);
+ }
+
+ let mut it = v.windows(2);
+ while let Some([..]) = it.next() {}
+
+ let v = vec![[1], [2], [3]];
+ let mut it = v.iter();
+ while let Some([1]) = it.next() {}
+
+ let mut it = v.iter();
+ while let Some([_x]) = it.next() {}
+ }
+
+ // binding
+ {
+ let v = vec![1, 2, 3];
+ let mut it = v.iter();
+ while let Some(x @ 1) = it.next() {
+ println!("{}", x);
+ }
+
+ let v = vec![[1], [2], [3]];
+ let mut it = v.iter();
+ while let Some(x @ [_]) = it.next() {
+ println!("{:?}", x);
+ }
+ }
+
+ // false negative
+ {
+ let v = vec![1, 2, 3];
+ let mut it = v.iter().map(Some);
+ while let Some(Some(_) | None) = it.next() {
+ println!("1");
+ }
+ }
+}
+
+fn nested_loops() {
+ let a = [42, 1337];
+
+ loop {
+ let mut y = a.iter();
+ while let Some(_) = y.next() {
+ // use a for loop here
+ }
+ }
+}
+
+fn issue1121() {
+ use std::collections::HashSet;
+ let mut values = HashSet::new();
+ values.insert(1);
+
+ while let Some(&value) = values.iter().next() {
+ values.remove(&value);
+ }
+}
+
+fn issue2965() {
+ // This should not cause an ICE
+
+ use std::collections::HashSet;
+ let mut values = HashSet::new();
+ values.insert(1);
+
+ while let Some(..) = values.iter().next() {}
+}
+
+fn issue3670() {
+ let array = [Some(0), None, Some(1)];
+ let mut iter = array.iter();
+
+ while let Some(elem) = iter.next() {
+ let _ = elem.or_else(|| *iter.next()?);
+ }
+}
+
+fn issue1654() {
+ // should not lint if the iterator is generated on every iteration
+ use std::collections::HashSet;
+ let mut values = HashSet::new();
+ values.insert(1);
+
+ while let Some(..) = values.iter().next() {
+ values.remove(&1);
+ }
+
+ while let Some(..) = values.iter().map(|x| x + 1).next() {}
+
+ let chars = "Hello, World!".char_indices();
+ while let Some((i, ch)) = chars.clone().next() {
+ println!("{}: {}", i, ch);
+ }
+}
+
+fn issue6491() {
+ // Used in outer loop, needs &mut
+ let mut it = 1..40;
+ while let Some(n) = it.next() {
+ while let Some(m) = it.next() {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+ println!("n still is {}", n);
+ }
+
+ // This is fine, inner loop uses a new iterator.
+ let mut it = 1..40;
+ while let Some(n) = it.next() {
+ let mut it = 1..40;
+ while let Some(m) = it.next() {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+
+ // Weird binding shouldn't change anything.
+ let (mut it, _) = (1..40, 0);
+ while let Some(m) = it.next() {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+
+ // Used after the loop, needs &mut.
+ let mut it = 1..40;
+ while let Some(m) = it.next() {
+ if m % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+ println!("next item {}", it.next().unwrap());
+
+ println!("n still is {}", n);
+ }
+}
+
+fn issue6231() {
+ // Closure in the outer loop, needs &mut
+ let mut it = 1..40;
+ let mut opt = Some(0);
+ while let Some(n) = opt.take().or_else(|| it.next()) {
+ while let Some(m) = it.next() {
+ if n % 10 == 0 {
+ break;
+ }
+ println!("doing something with m: {}", m);
+ }
+ println!("n still is {}", n);
+ }
+}
+
+fn issue1924() {
+ struct S<T>(T);
+ impl<T: Iterator<Item = u32>> S<T> {
+ fn f(&mut self) -> Option<u32> {
+ // Used as a field.
+ while let Some(i) = self.0.next() {
+ if !(3..8).contains(&i) {
+ return Some(i);
+ }
+ }
+ None
+ }
+
+ fn f2(&mut self) -> Option<u32> {
+ // Don't lint, self borrowed inside the loop
+ while let Some(i) = self.0.next() {
+ if i == 1 {
+ return self.f();
+ }
+ }
+ None
+ }
+ }
+ impl<T: Iterator<Item = u32>> S<(S<T>, Option<u32>)> {
+ fn f3(&mut self) -> Option<u32> {
+ // Don't lint, self borrowed inside the loop
+ while let Some(i) = self.0.0.0.next() {
+ if i == 1 {
+ return self.0.0.f();
+ }
+ }
+ while let Some(i) = self.0.0.0.next() {
+ if i == 1 {
+ return self.f3();
+ }
+ }
+ // This one is fine, a different field is borrowed
+ while let Some(i) = self.0.0.0.next() {
+ if i == 1 {
+ return self.0.1.take();
+ } else {
+ self.0.1 = Some(i);
+ }
+ }
+ None
+ }
+ }
+
+ struct S2<T>(T, u32);
+ impl<T: Iterator<Item = u32>> Iterator for S2<T> {
+ type Item = u32;
+ fn next(&mut self) -> Option<u32> {
+ self.0.next()
+ }
+ }
+
+ // Don't lint, field of the iterator is accessed in the loop
+ let mut it = S2(1..40, 0);
+ while let Some(n) = it.next() {
+ if n == it.1 {
+ break;
+ }
+ }
+
+ // Needs &mut, field of the iterator is accessed after the loop
+ let mut it = S2(1..40, 0);
+ while let Some(n) = it.next() {
+ if n == 0 {
+ break;
+ }
+ }
+ println!("iterator field {}", it.1);
+}
+
+fn issue7249() {
+ let mut it = 0..10;
+ let mut x = || {
+ // Needs &mut, the closure can be called multiple times
+ while let Some(x) = it.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ };
+ x();
+ x();
+}
+
+fn issue7510() {
+ let mut it = 0..10;
+ let it = &mut it;
+ // Needs to reborrow `it` as the binding isn't mutable
+ while let Some(x) = it.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ println!("{}", it.next().unwrap());
+
+ struct S<T>(T);
+ let mut it = 0..10;
+ let it = S(&mut it);
+ // Needs to reborrow `it.0` as the binding isn't mutable
+ while let Some(x) = it.0.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ println!("{}", it.0.next().unwrap());
+}
+
+fn exact_match_with_single_field() {
+ struct S<T>(T);
+ let mut s = S(0..10);
+ // Don't lint. `s.0` is used inside the loop.
+ while let Some(_) = s.0.next() {
+ let _ = &mut s.0;
+ }
+}
+
+fn custom_deref() {
+ struct S1<T> {
+ x: T,
+ }
+ struct S2<T>(S1<T>);
+ impl<T> core::ops::Deref for S2<T> {
+ type Target = S1<T>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+ impl<T> core::ops::DerefMut for S2<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+ }
+
+ let mut s = S2(S1 { x: 0..10 });
+ while let Some(x) = s.x.next() {
+ println!("{}", x);
+ }
+}
+
+fn issue_8113() {
+ let mut x = [0..10];
+ while let Some(x) = x[0].next() {
+ println!("{}", x);
+ }
+}
+
+fn fn_once_closure() {
+ let mut it = 0..10;
+ (|| {
+ while let Some(x) = it.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ })();
+
+ fn f(_: impl FnOnce()) {}
+ let mut it = 0..10;
+ f(|| {
+ while let Some(x) = it.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ });
+
+ fn f2(_: impl FnMut()) {}
+ let mut it = 0..10;
+ f2(|| {
+ while let Some(x) = it.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ });
+
+ fn f3(_: fn()) {}
+ f3(|| {
+ let mut it = 0..10;
+ while let Some(x) = it.next() {
+ if x % 2 == 0 {
+ break;
+ }
+ }
+ })
+}
+
+fn main() {
+ let mut it = 0..20;
+ while let Some(..) = it.next() {
+ println!("test");
+ }
+}
--- /dev/null
- --> $DIR/while_let_on_iterator.rs:16:5
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:21:5
++ --> $DIR/while_let_on_iterator.rs:14:5
+ |
+LL | while let Option::Some(x) = iter.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter`
+ |
+ = note: `-D clippy::while-let-on-iterator` implied by `-D warnings`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:26:5
++ --> $DIR/while_let_on_iterator.rs:19:5
+ |
+LL | while let Some(x) = iter.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:102:9
++ --> $DIR/while_let_on_iterator.rs:24:5
+ |
+LL | while let Some(_) = iter.next() {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:109:9
++ --> $DIR/while_let_on_iterator.rs:100:9
+ |
+LL | while let Some([..]) = it.next() {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [..] in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:122:9
++ --> $DIR/while_let_on_iterator.rs:107:9
+ |
+LL | while let Some([_x]) = it.next() {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [_x] in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:142:9
++ --> $DIR/while_let_on_iterator.rs:120:9
+ |
+LL | while let Some(x @ [_]) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x @ [_] in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:199:9
++ --> $DIR/while_let_on_iterator.rs:140:9
+ |
+LL | while let Some(_) = y.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:210:5
++ --> $DIR/while_let_on_iterator.rs:197:9
+ |
+LL | while let Some(m) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:212:9
++ --> $DIR/while_let_on_iterator.rs:208:5
+ |
+LL | while let Some(n) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:221:9
++ --> $DIR/while_let_on_iterator.rs:210:9
+ |
+LL | while let Some(m) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:230:9
++ --> $DIR/while_let_on_iterator.rs:219:9
+ |
+LL | while let Some(m) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:247:9
++ --> $DIR/while_let_on_iterator.rs:228:9
+ |
+LL | while let Some(m) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:262:13
++ --> $DIR/while_let_on_iterator.rs:245:9
+ |
+LL | while let Some(m) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:294:13
++ --> $DIR/while_let_on_iterator.rs:260:13
+ |
+LL | while let Some(i) = self.0.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:323:5
++ --> $DIR/while_let_on_iterator.rs:292:13
+ |
+LL | while let Some(i) = self.0.0.0.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.0.0.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:335:9
++ --> $DIR/while_let_on_iterator.rs:321:5
+ |
+LL | while let Some(n) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:349:5
++ --> $DIR/while_let_on_iterator.rs:333:9
+ |
+LL | while let Some(x) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:360:5
++ --> $DIR/while_let_on_iterator.rs:347:5
+ |
+LL | while let Some(x) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:395:5
++ --> $DIR/while_let_on_iterator.rs:358:5
+ |
+LL | while let Some(x) = it.0.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.0.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:402:5
++ --> $DIR/while_let_on_iterator.rs:393:5
+ |
+LL | while let Some(x) = s.x.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in s.x.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:410:9
++ --> $DIR/while_let_on_iterator.rs:400:5
+ |
+LL | while let Some(x) = x[0].next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in x[0].by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:420:9
++ --> $DIR/while_let_on_iterator.rs:408:9
+ |
+LL | while let Some(x) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:430:9
++ --> $DIR/while_let_on_iterator.rs:418:9
+ |
+LL | while let Some(x) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:440:9
++ --> $DIR/while_let_on_iterator.rs:428:9
+ |
+LL | while let Some(x) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()`
+
+error: this loop could be written as a `for` loop
- --> $DIR/while_let_on_iterator.rs:450:5
++ --> $DIR/while_let_on_iterator.rs:438:9
+ |
+LL | while let Some(x) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it`
+
+error: this loop could be written as a `for` loop
++ --> $DIR/while_let_on_iterator.rs:448:5
+ |
+LL | while let Some(..) = it.next() {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it`
+
+error: aborting due to 26 previous errors
+
--- /dev/null
-
+// run-rustfix
+// aux-build:non-exhaustive-enum.rs
- unreachable_code,
- unused_variables,
- dead_code,
+#![deny(clippy::wildcard_enum_match_arm)]
++#![allow(dead_code, unreachable_code, unused_variables)]
+#![allow(
- clippy::wildcard_in_or_patterns,
++ clippy::diverging_sub_expression,
+ clippy::single_match,
- clippy::diverging_sub_expression
++ clippy::uninlined_format_args,
+ clippy::unnested_or_patterns,
++ clippy::wildcard_in_or_patterns
+)]
+
+extern crate non_exhaustive_enum;
+
+use non_exhaustive_enum::ErrorKind;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum Color {
+ Red,
+ Green,
+ Blue,
+ Rgb(u8, u8, u8),
+ Cyan,
+}
+
+impl Color {
+ fn is_monochrome(self) -> bool {
+ match self {
+ Color::Red | Color::Green | Color::Blue => true,
+ Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0,
+ Color::Cyan => false,
+ }
+ }
+}
+
+fn main() {
+ let color = Color::Rgb(0, 0, 127);
+ match color {
+ Color::Red => println!("Red"),
+ Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan => eprintln!("Not red"),
+ };
+ match color {
+ Color::Red => println!("Red"),
+ _not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan => eprintln!("Not red"),
+ };
+ let _str = match color {
+ Color::Red => "Red".to_owned(),
+ not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan => format!("{:?}", not_red),
+ };
+ match color {
+ Color::Red => {},
+ Color::Green => {},
+ Color::Blue => {},
+ Color::Cyan => {},
+ c if c.is_monochrome() => {},
+ Color::Rgb(_, _, _) => {},
+ };
+ let _str = match color {
+ Color::Red => "Red",
+ c @ Color::Green | c @ Color::Blue | c @ Color::Rgb(_, _, _) | c @ Color::Cyan => "Not red",
+ };
+ match color {
+ Color::Rgb(r, _, _) if r > 0 => "Some red",
+ Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan => "No red",
+ };
+ match color {
+ Color::Red | Color::Green | Color::Blue | Color::Cyan => {},
+ Color::Rgb(..) => {},
+ };
+ let x: u8 = unimplemented!();
+ match x {
+ 0 => {},
+ 140 => {},
+ _ => {},
+ };
+ // We need to use an enum not defined in this test because non_exhaustive is ignored for the
+ // purposes of dead code analysis within a crate.
+ let error_kind = ErrorKind::NotFound;
+ match error_kind {
+ ErrorKind::NotFound => {},
+ ErrorKind::PermissionDenied | _ => {},
+ }
+ match error_kind {
+ ErrorKind::NotFound => {},
+ ErrorKind::PermissionDenied => {},
+ _ => {},
+ }
+
+ {
+ #![allow(clippy::manual_non_exhaustive)]
+ pub enum Enum {
+ A,
+ B,
+ #[doc(hidden)]
+ __Private,
+ }
+ match Enum::A {
+ Enum::A => (),
+ Enum::B | _ => (),
+ }
+ }
+}
--- /dev/null
-
+// run-rustfix
+// aux-build:non-exhaustive-enum.rs
- unreachable_code,
- unused_variables,
- dead_code,
+#![deny(clippy::wildcard_enum_match_arm)]
++#![allow(dead_code, unreachable_code, unused_variables)]
+#![allow(
- clippy::wildcard_in_or_patterns,
++ clippy::diverging_sub_expression,
+ clippy::single_match,
- clippy::diverging_sub_expression
++ clippy::uninlined_format_args,
+ clippy::unnested_or_patterns,
++ clippy::wildcard_in_or_patterns
+)]
+
+extern crate non_exhaustive_enum;
+
+use non_exhaustive_enum::ErrorKind;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum Color {
+ Red,
+ Green,
+ Blue,
+ Rgb(u8, u8, u8),
+ Cyan,
+}
+
+impl Color {
+ fn is_monochrome(self) -> bool {
+ match self {
+ Color::Red | Color::Green | Color::Blue => true,
+ Color::Rgb(r, g, b) => r | g == 0 || r | b == 0 || g | b == 0,
+ Color::Cyan => false,
+ }
+ }
+}
+
+fn main() {
+ let color = Color::Rgb(0, 0, 127);
+ match color {
+ Color::Red => println!("Red"),
+ _ => eprintln!("Not red"),
+ };
+ match color {
+ Color::Red => println!("Red"),
+ _not_red => eprintln!("Not red"),
+ };
+ let _str = match color {
+ Color::Red => "Red".to_owned(),
+ not_red => format!("{:?}", not_red),
+ };
+ match color {
+ Color::Red => {},
+ Color::Green => {},
+ Color::Blue => {},
+ Color::Cyan => {},
+ c if c.is_monochrome() => {},
+ Color::Rgb(_, _, _) => {},
+ };
+ let _str = match color {
+ Color::Red => "Red",
+ c @ Color::Green | c @ Color::Blue | c @ Color::Rgb(_, _, _) | c @ Color::Cyan => "Not red",
+ };
+ match color {
+ Color::Rgb(r, _, _) if r > 0 => "Some red",
+ _ => "No red",
+ };
+ match color {
+ Color::Red | Color::Green | Color::Blue | Color::Cyan => {},
+ Color::Rgb(..) => {},
+ };
+ let x: u8 = unimplemented!();
+ match x {
+ 0 => {},
+ 140 => {},
+ _ => {},
+ };
+ // We need to use an enum not defined in this test because non_exhaustive is ignored for the
+ // purposes of dead code analysis within a crate.
+ let error_kind = ErrorKind::NotFound;
+ match error_kind {
+ ErrorKind::NotFound => {},
+ _ => {},
+ }
+ match error_kind {
+ ErrorKind::NotFound => {},
+ ErrorKind::PermissionDenied => {},
+ _ => {},
+ }
+
+ {
+ #![allow(clippy::manual_non_exhaustive)]
+ pub enum Enum {
+ A,
+ B,
+ #[doc(hidden)]
+ __Private,
+ }
+ match Enum::A {
+ Enum::A => (),
+ _ => (),
+ }
+ }
+}
--- /dev/null
- --> $DIR/wildcard_enum_match_arm.rs:42:9
+error: wildcard match will also match any future added variants
- --> $DIR/wildcard_enum_match_arm.rs:4:9
++ --> $DIR/wildcard_enum_match_arm.rs:40:9
+ |
+LL | _ => eprintln!("Not red"),
+ | ^ help: try this: `Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan`
+ |
+note: the lint level is defined here
- --> $DIR/wildcard_enum_match_arm.rs:46:9
++ --> $DIR/wildcard_enum_match_arm.rs:3:9
+ |
+LL | #![deny(clippy::wildcard_enum_match_arm)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: wildcard match will also match any future added variants
- --> $DIR/wildcard_enum_match_arm.rs:50:9
++ --> $DIR/wildcard_enum_match_arm.rs:44:9
+ |
+LL | _not_red => eprintln!("Not red"),
+ | ^^^^^^^^ help: try this: `_not_red @ Color::Green | _not_red @ Color::Blue | _not_red @ Color::Rgb(..) | _not_red @ Color::Cyan`
+
+error: wildcard match will also match any future added variants
- --> $DIR/wildcard_enum_match_arm.rs:66:9
++ --> $DIR/wildcard_enum_match_arm.rs:48:9
+ |
+LL | not_red => format!("{:?}", not_red),
+ | ^^^^^^^ help: try this: `not_red @ Color::Green | not_red @ Color::Blue | not_red @ Color::Rgb(..) | not_red @ Color::Cyan`
+
+error: wildcard match will also match any future added variants
- --> $DIR/wildcard_enum_match_arm.rs:83:9
++ --> $DIR/wildcard_enum_match_arm.rs:64:9
+ |
+LL | _ => "No red",
+ | ^ help: try this: `Color::Red | Color::Green | Color::Blue | Color::Rgb(..) | Color::Cyan`
+
+error: wildcard matches known variants and will also match future added variants
- --> $DIR/wildcard_enum_match_arm.rs:101:13
++ --> $DIR/wildcard_enum_match_arm.rs:81:9
+ |
+LL | _ => {},
+ | ^ help: try this: `ErrorKind::PermissionDenied | _`
+
+error: wildcard matches known variants and will also match future added variants
++ --> $DIR/wildcard_enum_match_arm.rs:99:13
+ |
+LL | _ => (),
+ | ^ help: try this: `Enum::B | _`
+
+error: aborting due to 6 previous errors
+
--- /dev/null
- #![allow(unused_must_use)]
+#![warn(clippy::write_literal)]
++#![allow(clippy::uninlined_format_args, unused_must_use)]
+
+use std::io::Write;
+
+fn main() {
+ let mut v = Vec::new();
+
+ // these should be fine
+ write!(v, "Hello");
+ writeln!(v, "Hello");
+ let world = "world";
+ writeln!(v, "Hello {}", world);
+ writeln!(v, "Hello {world}", world = world);
+ writeln!(v, "3 in hex is {:X}", 3);
+ writeln!(v, "2 + 1 = {:.4}", 3);
+ writeln!(v, "2 + 1 = {:5.4}", 3);
+ writeln!(v, "Debug test {:?}", "hello, world");
+ writeln!(v, "{0:8} {1:>8}", "hello", "world");
+ writeln!(v, "{1:8} {0:>8}", "hello", "world");
+ writeln!(v, "{foo:8} {bar:>8}", foo = "hello", bar = "world");
+ writeln!(v, "{bar:8} {foo:>8}", foo = "hello", bar = "world");
+ writeln!(v, "{number:>width$}", number = 1, width = 6);
+ writeln!(v, "{number:>0width$}", number = 1, width = 6);
+ writeln!(v, "{} of {:b} people know binary, the other half doesn't", 1, 2);
+ writeln!(v, "10 / 4 is {}", 2.5);
+ writeln!(v, "2 + 1 = {}", 3);
+ writeln!(v, "From expansion {}", stringify!(not a string literal));
+
+ // these should throw warnings
+ write!(v, "Hello {}", "world");
+ writeln!(v, "Hello {} {}", world, "world");
+ writeln!(v, "Hello {}", "world");
+ writeln!(v, "{} {:.4}", "a literal", 5);
+
+ // positional args don't change the fact
+ // that we're using a literal -- this should
+ // throw a warning
+ writeln!(v, "{0} {1}", "hello", "world");
+ writeln!(v, "{1} {0}", "hello", "world");
+
+ // named args shouldn't change anything either
+ writeln!(v, "{foo} {bar}", foo = "hello", bar = "world");
+ writeln!(v, "{bar} {foo}", foo = "hello", bar = "world");
+}
--- /dev/null
- let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("error reading `{}`: {:?}", path, e));
+#![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]
+fn check_that_clippy_lints_and_clippy_utils_have_the_same_version_as_clippy() {
+ fn read_version(path: &str) -> String {
- .unwrap_or_else(|| panic!("error finding version in `{}`", path))
++ 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()))
- panic!("Failed to parse rustc version: {:?}", vsplit);
++ .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");
+ let clippy_lints_version = read_version("clippy_lints/Cargo.toml");
+ let clippy_utils_version = read_version("clippy_utils/Cargo.toml");
+
+ assert_eq!(clippy_version, clippy_lints_version);
+ assert_eq!(clippy_version, clippy_utils_version);
+}
+
+#[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:?}");
+ },
+ };
+}