@@ -3,7 +3,7 @@ use clippy_utils::trait_ref_of_method;
33use rustc_hir as hir;
44use rustc_lint:: { LateContext , LateLintPass } ;
55use rustc_middle:: ty:: TypeFoldable ;
6- use rustc_middle:: ty:: { Adt , Array , RawPtr , Ref , Slice , Tuple , Ty , TypeAndMut } ;
6+ use rustc_middle:: ty:: { Adt , Array , Ref , Slice , Tuple , Ty } ;
77use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
88use rustc_span:: source_map:: Span ;
99use rustc_span:: symbol:: sym;
@@ -19,10 +19,29 @@ declare_clippy_lint! {
1919 /// so having types with interior mutability is a bad idea.
2020 ///
2121 /// ### Known problems
22- /// It's correct to use a struct, that contains interior mutability
23- /// as a key, when its `Hash` implementation doesn't access any of the interior mutable types.
24- /// However, this lint is unable to recognize this, so it causes a false positive in theses cases.
25- /// The `bytes` crate is a great example of this.
22+ ///
23+ /// #### False Positives
24+ /// It's correct to use a struct that contains interior mutability as a key, when its
25+ /// implementation of `Hash` or `Ord` doesn't access any of the interior mutable types.
26+ /// However, this lint is unable to recognize this, so it will often cause false positives in
27+ /// theses cases. The `bytes` crate is a great example of this.
28+ ///
29+ /// #### False Negatives
30+ /// For custom `struct`s/`enum`s, this lint is unable to check for interior mutability behind
31+ /// indirection. For example, `struct BadKey<'a>(&'a Cell<usize>)` will be seen as immutable
32+ /// and cause a false negative if its implementation of `Hash`/`Ord` accesses the `Cell`.
33+ ///
34+ /// This lint does check a few cases for indirection. Firstly, using some standard library
35+ /// types (`Option`, `Result`, `Box`, `Rc`, `Arc`, `Vec`, `VecDeque`, `BTreeMap` and
36+ /// `BTreeSet`) directly as keys (e.g. in `HashMap<Box<Cell<usize>>, ()>`) **will** trigger the
37+ /// lint, because the impls of `Hash`/`Ord` for these types directly call `Hash`/`Ord` on their
38+ /// contained type.
39+ ///
40+ /// Secondly, the implementations of `Hash` and `Ord` for raw pointers (`*const T` or `*mut T`)
41+ /// apply only to the **address** of the contained value. Therefore, interior mutability
42+ /// behind raw pointers (e.g. in `HashSet<*mut Cell<usize>>`) can't impact the value of `Hash`
43+ /// or `Ord`, and therefore will not trigger this link. For more info, see issue
44+ /// [#6745](https://github.com/rust-lang/rust-clippy/issues/6745).
2645 ///
2746 /// ### Example
2847 /// ```rust
@@ -103,30 +122,52 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::
103122fn check_ty < ' tcx > ( cx : & LateContext < ' tcx > , span : Span , ty : Ty < ' tcx > ) {
104123 let ty = ty. peel_refs ( ) ;
105124 if let Adt ( def, substs) = ty. kind ( ) {
106- if [ sym:: hashmap_type, sym:: BTreeMap , sym:: hashset_type, sym:: BTreeMap ]
125+ let is_keyed_type = [ sym:: hashmap_type, sym:: BTreeMap , sym:: hashset_type, sym:: BTreeSet ]
107126 . iter ( )
108- . any ( |diag_item| cx. tcx . is_diagnostic_item ( * diag_item, def. did ) )
109- && is_mutable_type ( cx, substs. type_at ( 0 ) , span)
110- {
127+ . any ( |diag_item| cx. tcx . is_diagnostic_item ( * diag_item, def. did ) ) ;
128+ if is_keyed_type && is_interior_mutable_type ( cx, substs. type_at ( 0 ) , span) {
111129 span_lint ( cx, MUTABLE_KEY_TYPE , span, "mutable key type" ) ;
112130 }
113131 }
114132}
115133
116- fn is_mutable_type < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > , span : Span ) -> bool {
134+ /// Determines if a type contains interior mutability which would affect its implementation of
135+ /// [`Hash`] or [`Ord`].
136+ fn is_interior_mutable_type < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > , span : Span ) -> bool {
117137 match * ty. kind ( ) {
118- RawPtr ( TypeAndMut { ty : inner_ty, mutbl } ) | Ref ( _, inner_ty, mutbl) => {
119- mutbl == hir:: Mutability :: Mut || is_mutable_type ( cx, inner_ty, span)
120- } ,
121- Slice ( inner_ty) => is_mutable_type ( cx, inner_ty, span) ,
138+ Ref ( _, inner_ty, mutbl) => mutbl == hir:: Mutability :: Mut || is_interior_mutable_type ( cx, inner_ty, span) ,
139+ Slice ( inner_ty) => is_interior_mutable_type ( cx, inner_ty, span) ,
122140 Array ( inner_ty, size) => {
123- size. try_eval_usize ( cx. tcx , cx. param_env ) . map_or ( true , |u| u != 0 ) && is_mutable_type ( cx, inner_ty, span)
141+ size. try_eval_usize ( cx. tcx , cx. param_env ) . map_or ( true , |u| u != 0 )
142+ && is_interior_mutable_type ( cx, inner_ty, span)
124143 } ,
125- Tuple ( ..) => ty. tuple_fields ( ) . any ( |ty| is_mutable_type ( cx, ty, span) ) ,
126- Adt ( ..) => {
127- !ty. has_escaping_bound_vars ( )
128- && cx. tcx . layout_of ( cx. param_env . and ( ty) ) . is_ok ( )
129- && !ty. is_freeze ( cx. tcx . at ( span) , cx. param_env )
144+ Tuple ( ..) => ty. tuple_fields ( ) . any ( |ty| is_interior_mutable_type ( cx, ty, span) ) ,
145+ Adt ( def, substs) => {
146+ // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
147+ // that of their type parameters. Note: we don't include `HashSet` and `HashMap`
148+ // because they have no impl for `Hash` or `Ord`.
149+ let is_std_collection = [
150+ sym:: option_type,
151+ sym:: result_type,
152+ sym:: LinkedList ,
153+ sym:: vec_type,
154+ sym:: vecdeque_type,
155+ sym:: BTreeMap ,
156+ sym:: BTreeSet ,
157+ sym:: Rc ,
158+ sym:: Arc ,
159+ ]
160+ . iter ( )
161+ . any ( |diag_item| cx. tcx . is_diagnostic_item ( * diag_item, def. did ) ) ;
162+ let is_box = Some ( def. did ) == cx. tcx . lang_items ( ) . owned_box ( ) ;
163+ if is_std_collection || is_box {
164+ // The type is mutable if any of its type parameters are
165+ substs. types ( ) . any ( |ty| is_interior_mutable_type ( cx, ty, span) )
166+ } else {
167+ !ty. has_escaping_bound_vars ( )
168+ && cx. tcx . layout_of ( cx. param_env . and ( ty) ) . is_ok ( )
169+ && !ty. is_freeze ( cx. tcx . at ( span) , cx. param_env )
170+ }
130171 } ,
131172 _ => false ,
132173 }
0 commit comments