@@ -3,7 +3,7 @@ use clippy_utils::trait_ref_of_method;
3
3
use rustc_hir as hir;
4
4
use rustc_lint:: { LateContext , LateLintPass } ;
5
5
use 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 } ;
7
7
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
8
8
use rustc_span:: source_map:: Span ;
9
9
use rustc_span:: symbol:: sym;
@@ -19,10 +19,29 @@ declare_clippy_lint! {
19
19
/// so having types with interior mutability is a bad idea.
20
20
///
21
21
/// ### 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).
26
45
///
27
46
/// ### Example
28
47
/// ```rust
@@ -103,43 +122,52 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::
103
122
fn check_ty < ' tcx > ( cx : & LateContext < ' tcx > , span : Span , ty : Ty < ' tcx > ) {
104
123
let ty = ty. peel_refs ( ) ;
105
124
if let Adt ( def, substs) = ty. kind ( ) {
106
- let is_map_type = [ 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 ]
107
126
. iter ( )
108
127
. any ( |diag_item| cx. tcx . is_diagnostic_item ( * diag_item, def. did ) ) ;
109
- if is_map_type && is_mutable_type ( cx, substs. type_at ( 0 ) , span, true ) {
128
+ if is_keyed_type && is_interior_mutable_type ( cx, substs. type_at ( 0 ) , span) {
110
129
span_lint ( cx, MUTABLE_KEY_TYPE , span, "mutable key type" ) ;
111
130
}
112
131
}
113
132
}
114
133
115
- fn is_mutable_type < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > , span : Span , is_top_level_type : bool ) -> 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 {
116
137
match * ty. kind ( ) {
117
- RawPtr ( TypeAndMut { ty : inner_ty, mutbl } ) => {
118
- if is_top_level_type {
119
- // Raw pointers are hashed by the address they point to, not what is pointed to.
120
- // Therefore, using a raw pointer to any type as the top-level key type is OK.
121
- // Using raw pointers _in_ the key type is not, because the wrapper type could
122
- // provide a custom `impl` for `Hash` (which could deref the raw pointer).
123
- //
124
- // see:
125
- // - clippy issue: https://github.com/rust-lang/rust-clippy/issues/6745
126
- // - std code: https://github.com/rust-lang/rust/blob/1.54.0/library/core/src/hash/mod.rs#L717-L736
127
- false
128
- } else {
129
- mutbl == hir:: Mutability :: Mut || is_mutable_type ( cx, inner_ty, span, false )
130
- }
131
- } ,
132
- Ref ( _, inner_ty, mutbl) => mutbl == hir:: Mutability :: Mut || is_mutable_type ( cx, inner_ty, span, false ) ,
133
- Slice ( inner_ty) => is_mutable_type ( cx, inner_ty, span, false ) ,
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) ,
134
140
Array ( inner_ty, size) => {
135
141
size. try_eval_usize ( cx. tcx , cx. param_env ) . map_or ( true , |u| u != 0 )
136
- && is_mutable_type ( cx, inner_ty, span, false )
142
+ && is_interior_mutable_type ( cx, inner_ty, span)
137
143
} ,
138
- Tuple ( ..) => ty. tuple_fields ( ) . any ( |ty| is_mutable_type ( cx, ty, span, false ) ) ,
139
- Adt ( ..) => {
140
- !ty. has_escaping_bound_vars ( )
141
- && cx. tcx . layout_of ( cx. param_env . and ( ty) ) . is_ok ( )
142
- && !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
+ }
143
171
} ,
144
172
_ => false ,
145
173
}
0 commit comments