5
5
use std:: ptr;
6
6
7
7
use rustc_hir:: def:: { DefKind , Res } ;
8
- use rustc_hir:: { Expr , ExprKind , ImplItem , ImplItemKind , Item , ItemKind , Node , TraitItem , TraitItemKind , UnOp } ;
8
+ use rustc_hir:: def_id:: DefId ;
9
+ use rustc_hir:: {
10
+ BodyId , Expr , ExprKind , HirId , ImplItem , ImplItemKind , Item , ItemKind , Node , TraitItem , TraitItemKind , UnOp ,
11
+ } ;
9
12
use rustc_infer:: traits:: specialization_graph;
10
13
use rustc_lint:: { LateContext , LateLintPass , Lint } ;
14
+ use rustc_middle:: mir:: interpret:: { ConstValue , ErrorHandled } ;
11
15
use rustc_middle:: ty:: adjustment:: Adjust ;
12
- use rustc_middle:: ty:: { AssocKind , Ty } ;
16
+ use rustc_middle:: ty:: { self , AssocKind , Const , Ty } ;
13
17
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
14
18
use rustc_span:: { InnerSpan , Span , DUMMY_SP } ;
15
19
use rustc_typeck:: hir_ty_to_ty;
@@ -36,15 +40,18 @@ declare_clippy_lint! {
36
40
/// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit,
37
41
/// and this lint should be suppressed.
38
42
///
39
- /// When an enum has variants with interior mutability, use of its non interior mutable
40
- /// variants can generate false positives. See issue
41
- /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962)
43
+ /// Even though the lint avoids triggering on a constant whose type has enums that have variants
44
+ /// with interior mutability, and its value uses non interior mutable variants (see
45
+ /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962) and
46
+ /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825) for examples);
47
+ /// it complains about associated constants without default values only based on its types;
48
+ /// which might not be preferable.
49
+ /// There're other enums plus associated constants cases that the lint cannot handle.
42
50
///
43
51
/// Types that have underlying or potential interior mutability trigger the lint whether
44
52
/// the interior mutable field is used or not. See issues
45
53
/// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and
46
- /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825)
47
- ///
54
+ ///
48
55
/// **Example:**
49
56
/// ```rust
50
57
/// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
@@ -105,6 +112,79 @@ declare_clippy_lint! {
105
112
"referencing `const` with interior mutability"
106
113
}
107
114
115
+ fn is_unfrozen < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> bool {
116
+ // Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
117
+ // making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
118
+ // 'unfrozen'. However, this code causes a false negative in which
119
+ // a type contains a layout-unknown type, but also a unsafe cell like `const CELL: Cell<T>`.
120
+ // Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
121
+ // since it works when a pointer indirection involves (`Cell<*const T>`).
122
+ // Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
123
+ // but I'm not sure whether it's a decent way, if possible.
124
+ cx. tcx . layout_of ( cx. param_env . and ( ty) ) . is_ok ( ) && !ty. is_freeze ( cx. tcx . at ( DUMMY_SP ) , cx. param_env )
125
+ }
126
+
127
+ fn is_value_unfrozen_raw < ' tcx > (
128
+ cx : & LateContext < ' tcx > ,
129
+ result : Result < ConstValue < ' tcx > , ErrorHandled > ,
130
+ ty : Ty < ' tcx > ,
131
+ ) -> bool {
132
+ fn inner < ' tcx > ( cx : & LateContext < ' tcx > , val : & ' tcx Const < ' tcx > ) -> bool {
133
+ match val. ty . kind ( ) {
134
+ // the fact that we have to dig into every structs to search enums
135
+ // leads us to the point checking `UnsafeCell` directly is the only option.
136
+ ty:: Adt ( ty_def, ..) if Some ( ty_def. did ) == cx. tcx . lang_items ( ) . unsafe_cell_type ( ) => true ,
137
+ ty:: Array ( ..) | ty:: Adt ( ..) | ty:: Tuple ( ..) => {
138
+ let val = cx. tcx . destructure_const ( cx. param_env . and ( val) ) ;
139
+ val. fields . iter ( ) . any ( |field| inner ( cx, field) )
140
+ } ,
141
+ _ => false ,
142
+ }
143
+ }
144
+
145
+ result. map_or_else (
146
+ |err| {
147
+ // Consider `TooGeneric` cases as being unfrozen.
148
+ // This causes a false positive where an assoc const whose type is unfrozen
149
+ // have a value that is a frozen variant with a generic param (an example is
150
+ // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
151
+ // However, it prevents a number of false negatives that is, I think, important:
152
+ // 1. assoc consts in trait defs referring to consts of themselves
153
+ // (an example is `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
154
+ // 2. a path expr referring to assoc consts whose type is doesn't have
155
+ // any frozen variants in trait defs (i.e. without substitute for `Self`).
156
+ // (e.g. borrowing `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
157
+ // 3. similar to the false positive above;
158
+ // but the value is an unfrozen variant, or the type has no enums. (An example is
159
+ // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT`
160
+ // and `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
161
+ // One might be able to prevent these FNs correctly, and replace this with `false`;
162
+ // e.g. implementing `has_frozen_variant` described above, and not running this function
163
+ // when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
164
+ // case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
165
+ // similar to 2., but with the a frozen variant) (e.g. borrowing
166
+ // `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
167
+ // I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
168
+ err == ErrorHandled :: TooGeneric
169
+ } ,
170
+ |val| inner ( cx, Const :: from_value ( cx. tcx , val, ty) ) ,
171
+ )
172
+ }
173
+
174
+ fn is_value_unfrozen_poly < ' tcx > ( cx : & LateContext < ' tcx > , body_id : BodyId , ty : Ty < ' tcx > ) -> bool {
175
+ let result = cx. tcx . const_eval_poly ( body_id. hir_id . owner . to_def_id ( ) ) ;
176
+ is_value_unfrozen_raw ( cx, result, ty)
177
+ }
178
+
179
+ fn is_value_unfrozen_expr < ' tcx > ( cx : & LateContext < ' tcx > , hir_id : HirId , def_id : DefId , ty : Ty < ' tcx > ) -> bool {
180
+ let substs = cx. typeck_results ( ) . node_substs ( hir_id) ;
181
+
182
+ let result = cx
183
+ . tcx
184
+ . const_eval_resolve ( cx. param_env , ty:: WithOptConstParam :: unknown ( def_id) , substs, None , None ) ;
185
+ is_value_unfrozen_raw ( cx, result, ty)
186
+ }
187
+
108
188
#[ derive( Copy , Clone ) ]
109
189
enum Source {
110
190
Item { item : Span } ,
@@ -130,19 +210,7 @@ impl Source {
130
210
}
131
211
}
132
212
133
- fn verify_ty_bound < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > , source : Source ) {
134
- // Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
135
- // making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
136
- // 'unfrozen'. However, this code causes a false negative in which
137
- // a type contains a layout-unknown type, but also a unsafe cell like `const CELL: Cell<T>`.
138
- // Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
139
- // since it works when a pointer indirection involves (`Cell<*const T>`).
140
- // Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
141
- // but I'm not sure whether it's a decent way, if possible.
142
- if cx. tcx . layout_of ( cx. param_env . and ( ty) ) . is_err ( ) || ty. is_freeze ( cx. tcx . at ( DUMMY_SP ) , cx. param_env ) {
143
- return ;
144
- }
145
-
213
+ fn lint ( cx : & LateContext < ' _ > , source : Source ) {
146
214
let ( lint, msg, span) = source. lint ( ) ;
147
215
span_lint_and_then ( cx, lint, span, msg, |diag| {
148
216
if span. from_expansion ( ) {
@@ -165,24 +233,44 @@ declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTER
165
233
166
234
impl < ' tcx > LateLintPass < ' tcx > for NonCopyConst {
167
235
fn check_item ( & mut self , cx : & LateContext < ' tcx > , it : & ' tcx Item < ' _ > ) {
168
- if let ItemKind :: Const ( hir_ty, .. ) = & it. kind {
236
+ if let ItemKind :: Const ( hir_ty, body_id ) = it. kind {
169
237
let ty = hir_ty_to_ty ( cx. tcx , hir_ty) ;
170
- verify_ty_bound ( cx, ty, Source :: Item { item : it. span } ) ;
238
+
239
+ if is_unfrozen ( cx, ty) && is_value_unfrozen_poly ( cx, body_id, ty) {
240
+ lint ( cx, Source :: Item { item : it. span } ) ;
241
+ }
171
242
}
172
243
}
173
244
174
245
fn check_trait_item ( & mut self , cx : & LateContext < ' tcx > , trait_item : & ' tcx TraitItem < ' _ > ) {
175
- if let TraitItemKind :: Const ( hir_ty, .. ) = & trait_item. kind {
246
+ if let TraitItemKind :: Const ( hir_ty, body_id_opt ) = & trait_item. kind {
176
247
let ty = hir_ty_to_ty ( cx. tcx , hir_ty) ;
248
+
177
249
// Normalize assoc types because ones originated from generic params
178
250
// bounded other traits could have their bound.
179
251
let normalized = cx. tcx . normalize_erasing_regions ( cx. param_env , ty) ;
180
- verify_ty_bound ( cx, normalized, Source :: Assoc { item : trait_item. span } ) ;
252
+ if is_unfrozen ( cx, normalized)
253
+ // When there's no default value, lint it only according to its type;
254
+ // in other words, lint consts whose value *could* be unfrozen, not definitely is.
255
+ // This feels inconsistent with how the lint treats generic types,
256
+ // which avoids linting types which potentially become unfrozen.
257
+ // One could check whether a unfrozen type have a *frozen variant*
258
+ // (like `body_id_opt.map_or_else(|| !has_frozen_variant(...), ...)`),
259
+ // and do the same as the case of generic types at impl items.
260
+ // Note that it isn't sufficient to check if it has an enum
261
+ // since all of that enum's variants can be unfrozen:
262
+ // i.e. having an enum doesn't necessary mean a type has a frozen variant.
263
+ // And, implementing it isn't a trivial task; it'll probably end up
264
+ // re-implementing the trait predicate evaluation specific to `Freeze`.
265
+ && body_id_opt. map_or ( true , |body_id| is_value_unfrozen_poly ( cx, body_id, normalized) )
266
+ {
267
+ lint ( cx, Source :: Assoc { item : trait_item. span } ) ;
268
+ }
181
269
}
182
270
}
183
271
184
272
fn check_impl_item ( & mut self , cx : & LateContext < ' tcx > , impl_item : & ' tcx ImplItem < ' _ > ) {
185
- if let ImplItemKind :: Const ( hir_ty, .. ) = & impl_item. kind {
273
+ if let ImplItemKind :: Const ( hir_ty, body_id ) = & impl_item. kind {
186
274
let item_hir_id = cx. tcx . hir ( ) . get_parent_node ( impl_item. hir_id ) ;
187
275
let item = cx. tcx . hir ( ) . expect_item ( item_hir_id) ;
188
276
@@ -209,24 +297,34 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
209
297
) ,
210
298
) )
211
299
. is_err( ) ;
300
+ // If there were a function like `has_frozen_variant` described above,
301
+ // we should use here as a frozen variant is a potential to be frozen
302
+ // similar to unknown layouts.
303
+ // e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
212
304
then {
213
305
let ty = hir_ty_to_ty( cx. tcx, hir_ty) ;
214
306
let normalized = cx. tcx. normalize_erasing_regions( cx. param_env, ty) ;
215
- verify_ty_bound(
216
- cx,
217
- normalized,
218
- Source :: Assoc {
219
- item: impl_item. span,
220
- } ,
221
- ) ;
307
+ if is_unfrozen( cx, normalized)
308
+ && is_value_unfrozen_poly( cx, * body_id, normalized)
309
+ {
310
+ lint(
311
+ cx,
312
+ Source :: Assoc {
313
+ item: impl_item. span,
314
+ } ,
315
+ ) ;
316
+ }
222
317
}
223
318
}
224
319
} ,
225
320
ItemKind :: Impl { of_trait : None , .. } => {
226
321
let ty = hir_ty_to_ty ( cx. tcx , hir_ty) ;
227
322
// Normalize assoc types originated from generic params.
228
323
let normalized = cx. tcx . normalize_erasing_regions ( cx. param_env , ty) ;
229
- verify_ty_bound ( cx, normalized, Source :: Assoc { item : impl_item. span } ) ;
324
+
325
+ if is_unfrozen ( cx, ty) && is_value_unfrozen_poly ( cx, * body_id, normalized) {
326
+ lint ( cx, Source :: Assoc { item : impl_item. span } ) ;
327
+ }
230
328
} ,
231
329
_ => ( ) ,
232
330
}
@@ -241,8 +339,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
241
339
}
242
340
243
341
// Make sure it is a const item.
244
- match qpath_res ( cx, qpath, expr. hir_id ) {
245
- Res :: Def ( DefKind :: Const | DefKind :: AssocConst , _ ) => { } ,
342
+ let item_def_id = match qpath_res ( cx, qpath, expr. hir_id ) {
343
+ Res :: Def ( DefKind :: Const | DefKind :: AssocConst , did ) => did ,
246
344
_ => return ,
247
345
} ;
248
346
@@ -319,7 +417,9 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
319
417
cx. typeck_results ( ) . expr_ty ( dereferenced_expr)
320
418
} ;
321
419
322
- verify_ty_bound ( cx, ty, Source :: Expr { expr : expr. span } ) ;
420
+ if is_unfrozen ( cx, ty) && is_value_unfrozen_expr ( cx, expr. hir_id , item_def_id, ty) {
421
+ lint ( cx, Source :: Expr { expr : expr. span } ) ;
422
+ }
323
423
}
324
424
}
325
425
}
0 commit comments