55use std:: ptr;
66
77use 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+ } ;
912use rustc_infer:: traits:: specialization_graph;
1013use rustc_lint:: { LateContext , LateLintPass , Lint } ;
14+ use rustc_middle:: mir:: interpret:: { ConstValue , ErrorHandled } ;
1115use rustc_middle:: ty:: adjustment:: Adjust ;
12- use rustc_middle:: ty:: { AssocKind , Ty } ;
16+ use rustc_middle:: ty:: { self , AssocKind , Const , Ty } ;
1317use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1418use rustc_span:: { InnerSpan , Span , DUMMY_SP } ;
1519use rustc_typeck:: hir_ty_to_ty;
@@ -36,14 +40,17 @@ declare_clippy_lint! {
3640 /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit,
3741 /// and this lint should be suppressed.
3842 ///
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.
4250 ///
4351 /// Types that have underlying or potential interior mutability trigger the lint whether
4452 /// the interior mutable field is used or not. See issues
4553 /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and
46- /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825)
4754 ///
4855 /// **Example:**
4956 /// ```rust
@@ -105,6 +112,79 @@ declare_clippy_lint! {
105112 "referencing `const` with interior mutability"
106113}
107114
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+
108188#[ derive( Copy , Clone ) ]
109189enum Source {
110190 Item { item : Span } ,
@@ -130,19 +210,7 @@ impl Source {
130210 }
131211}
132212
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 ) {
146214 let ( lint, msg, span) = source. lint ( ) ;
147215 span_lint_and_then ( cx, lint, span, msg, |diag| {
148216 if span. from_expansion ( ) {
@@ -165,24 +233,44 @@ declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTER
165233
166234impl < ' tcx > LateLintPass < ' tcx > for NonCopyConst {
167235 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 {
169237 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+ }
171242 }
172243 }
173244
174245 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 {
176247 let ty = hir_ty_to_ty ( cx. tcx , hir_ty) ;
248+
177249 // Normalize assoc types because ones originated from generic params
178250 // bounded other traits could have their bound.
179251 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+ }
181269 }
182270 }
183271
184272 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 {
186274 let item_hir_id = cx. tcx . hir ( ) . get_parent_node ( impl_item. hir_id ) ;
187275 let item = cx. tcx . hir ( ) . expect_item ( item_hir_id) ;
188276
@@ -209,24 +297,34 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
209297 ) ,
210298 ) )
211299 . 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(...);`
212304 then {
213305 let ty = hir_ty_to_ty( cx. tcx, hir_ty) ;
214306 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+ }
222317 }
223318 }
224319 } ,
225320 ItemKind :: Impl { of_trait : None , .. } => {
226321 let ty = hir_ty_to_ty ( cx. tcx , hir_ty) ;
227322 // Normalize assoc types originated from generic params.
228323 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+ }
230328 } ,
231329 _ => ( ) ,
232330 }
@@ -241,8 +339,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
241339 }
242340
243341 // 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 ,
246344 _ => return ,
247345 } ;
248346
@@ -319,7 +417,9 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
319417 cx. typeck_results ( ) . expr_ty ( dereferenced_expr)
320418 } ;
321419
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+ }
323423 }
324424 }
325425}
0 commit comments