Skip to content

Commit cdfe9d8

Browse files
committed
Add interior_mutable_consts lint
1 parent 6ce4269 commit cdfe9d8

7 files changed

+612
-5
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,14 @@ lint_incomplete_include =
436436
437437
lint_inner_macro_attribute_unstable = inner macro attributes are unstable
438438
439+
lint_interior_mutable_consts = interior mutability in `const` items have no effect on the `const` item itself
440+
.label = `{$ty_name}` is an interior mutable type
441+
.temporary = each usage of a `const` item creates a new temporary
442+
.never_original = only the temporaries and never the original `const` item `{$const_name}` will be modified
443+
.suggestion_inline_const = for use as an initializer, consider using an inline-const `const {"{"} /* ... */ {"}"}` at the usage site instead
444+
.suggestion_static = for a shared instance of `{$const_name}`, consider using a `static` item instead
445+
.suggestion_expect = alternatively consider expecting the lint
446+
439447
lint_invalid_asm_label_binary = avoid using labels containing only the digits `0` and `1` in inline assembly
440448
.label = use a different label that doesn't start with `0` or `1`
441449
.help = start numbering with `2` instead

compiler/rustc_lint/src/lints.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,6 +1940,55 @@ pub(crate) enum UnpredictableFunctionPointerComparisonsSuggestion<'a, 'tcx> {
19401940
},
19411941
}
19421942

1943+
#[derive(LintDiagnostic)]
1944+
#[diag(lint_interior_mutable_consts)]
1945+
#[note(lint_temporary)]
1946+
#[note(lint_never_original)]
1947+
#[help(lint_suggestion_inline_const)]
1948+
pub(crate) struct InteriorMutableConstsDiag {
1949+
pub ty_name: Ident,
1950+
pub const_name: Ident,
1951+
#[label]
1952+
pub ty_span: Span,
1953+
#[subdiagnostic]
1954+
pub sugg_static: InteriorMutableConstsSuggestionStatic,
1955+
#[subdiagnostic]
1956+
pub sugg_expect: InteriorMutableConstsSuggestionExpect,
1957+
}
1958+
1959+
#[derive(Subdiagnostic)]
1960+
pub(crate) enum InteriorMutableConstsSuggestionStatic {
1961+
#[suggestion(
1962+
lint_suggestion_static,
1963+
code = "{before}static ",
1964+
style = "verbose",
1965+
applicability = "maybe-incorrect"
1966+
)]
1967+
Spanful {
1968+
#[primary_span]
1969+
const_: Span,
1970+
before: &'static str,
1971+
},
1972+
#[help(lint_suggestion_static)]
1973+
Spanless,
1974+
}
1975+
1976+
#[derive(Subdiagnostic)]
1977+
pub(crate) enum InteriorMutableConstsSuggestionExpect {
1978+
#[suggestion(
1979+
lint_suggestion_expect,
1980+
code = "#[expect(interior_mutable_consts, reason = \"...\")] ",
1981+
style = "verbose",
1982+
applicability = "maybe-incorrect"
1983+
)]
1984+
Spanful {
1985+
#[primary_span]
1986+
span: Span,
1987+
},
1988+
#[help(lint_suggestion_expect)]
1989+
Spanless,
1990+
}
1991+
19431992
pub(crate) struct ImproperCTypes<'a> {
19441993
pub ty: Ty<'a>,
19451994
pub desc: &'a str,

compiler/rustc_lint/src/types.rs

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ use crate::lints::{
2626
AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion,
2727
AmbiguousWidePointerComparisonsAddrSuggestion, AmbiguousWidePointerComparisonsCastSuggestion,
2828
AmbiguousWidePointerComparisonsExpectSuggestion, AtomicOrderingFence, AtomicOrderingLoad,
29-
AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons,
30-
InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons,
31-
UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons, UsesPowerAlignment,
32-
VariantSizeDifferencesDiag,
29+
AtomicOrderingStore, ImproperCTypes, InteriorMutableConstsDiag,
30+
InteriorMutableConstsSuggestionExpect, InteriorMutableConstsSuggestionStatic,
31+
InvalidAtomicOrderingDiag, InvalidNanComparisons, InvalidNanComparisonsSuggestion,
32+
UnpredictableFunctionPointerComparisons, UnpredictableFunctionPointerComparisonsSuggestion,
33+
UnusedComparisons, UsesPowerAlignment, VariantSizeDifferencesDiag,
3334
};
3435
use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent};
3536

@@ -201,6 +202,47 @@ declare_lint! {
201202
"detects unpredictable function pointer comparisons"
202203
}
203204

205+
declare_lint! {
206+
/// The `interior_mutable_consts` lint detects instance where
207+
/// const-items have a interior mutable type, which silently does nothing.
208+
///
209+
/// ### Example
210+
///
211+
/// ```rust
212+
/// use std::sync::Once;
213+
///
214+
/// // SAFETY: should only be call once
215+
/// unsafe extern "C" fn ffi_init() { /* ... */ }
216+
///
217+
/// const A: Once = Once::new(); // using `B` will always creates temporaries and
218+
/// // never modify itself on use, should be a
219+
/// // static-item instead
220+
///
221+
/// fn init() {
222+
/// A.call_once(|| unsafe {
223+
/// ffi_init(); // unsound, as the `call_once` is on a temporary
224+
/// // and not on a shared variable
225+
/// })
226+
/// }
227+
/// ```
228+
///
229+
/// {{produces}}
230+
///
231+
/// ### Explanation
232+
///
233+
/// Using a const-item with an interior mutable type has no effect as const-item
234+
/// are essentially inlined wherever they are used, meaning that they are copied
235+
/// directly into the relevant context when used rendering modification through
236+
/// interior mutability ineffective across usage of that const-item.
237+
///
238+
/// The current implementation of this lint only warns on significant `std` and
239+
/// `core` interior mutable types, like `Once`, `AtomicI32`, ... this is done out
240+
/// of prudence and may be extended in the future.
241+
INTERIOR_MUTABLE_CONSTS,
242+
Warn,
243+
"detects const items with interior mutable type",
244+
}
245+
204246
#[derive(Copy, Clone, Default)]
205247
pub(crate) struct TypeLimits {
206248
/// Id of the last visited negated expression
@@ -214,7 +256,8 @@ impl_lint_pass!(TypeLimits => [
214256
OVERFLOWING_LITERALS,
215257
INVALID_NAN_COMPARISONS,
216258
AMBIGUOUS_WIDE_POINTER_COMPARISONS,
217-
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS
259+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
260+
INTERIOR_MUTABLE_CONSTS,
218261
]);
219262

220263
impl TypeLimits {
@@ -696,6 +739,48 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
696739
})
697740
}
698741
}
742+
743+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
744+
if let hir::ItemKind::Const(ident, ty, _generics, _body_id) = item.kind
745+
&& let hir::TyKind::Path(ref qpath) = ty.kind
746+
&& let Some(def_id) = cx.qpath_res(qpath, ty.hir_id).opt_def_id()
747+
&& cx.tcx.has_attr(def_id, sym::rustc_significant_interior_mutable_type)
748+
&& let Some(ty_name) = cx.tcx.opt_item_ident(def_id)
749+
{
750+
let (sugg_static, sugg_expect) = if let Some(vis_span) =
751+
item.vis_span.find_ancestor_inside(item.span)
752+
&& item.span.can_be_used_for_suggestions()
753+
&& vis_span.can_be_used_for_suggestions()
754+
{
755+
(
756+
InteriorMutableConstsSuggestionStatic::Spanful {
757+
const_: item.vis_span.between(ident.span),
758+
before: if !vis_span.is_empty() { " " } else { "" },
759+
},
760+
InteriorMutableConstsSuggestionExpect::Spanful {
761+
span: item.span.shrink_to_lo(),
762+
},
763+
)
764+
} else {
765+
(
766+
InteriorMutableConstsSuggestionStatic::Spanless,
767+
InteriorMutableConstsSuggestionExpect::Spanless,
768+
)
769+
};
770+
771+
cx.emit_span_lint(
772+
INTERIOR_MUTABLE_CONSTS,
773+
item.span,
774+
InteriorMutableConstsDiag {
775+
ty_name,
776+
ty_span: ty.span,
777+
const_name: ident,
778+
sugg_static,
779+
sugg_expect,
780+
},
781+
);
782+
}
783+
}
699784
}
700785

701786
declare_lint! {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ check-pass
2+
3+
use std::cell::{Cell, RefCell, UnsafeCell, LazyCell, OnceCell};
4+
5+
const A: Cell<i32> = Cell::new(0);
6+
//~^ WARN interior mutability in `const` item
7+
const B: RefCell<i32> = RefCell::new(0);
8+
//~^ WARN interior mutability in `const` item
9+
const C: UnsafeCell<i32> = UnsafeCell::new(0);
10+
//~^ WARN interior mutability in `const` item
11+
const D: LazyCell<i32> = LazyCell::new(|| 0);
12+
//~^ WARN interior mutability in `const` item
13+
const E: OnceCell<i32> = OnceCell::new();
14+
//~^ WARN interior mutability in `const` item
15+
16+
fn main() {}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
warning: interior mutability in `const` items have no effect on the `const` item itself
2+
--> $DIR/interior-mutable-types-in-consts-not-sync.rs:5:1
3+
|
4+
LL | const A: Cell<i32> = Cell::new(0);
5+
| ^^^^^^^^^---------^^^^^^^^^^^^^^^^
6+
| |
7+
| `Cell` is an interior mutable type
8+
|
9+
= note: each usage of a `const` item creates a new temporary
10+
= note: only the temporaries and never the original `const` item `A` will be modified
11+
= help: for use as an initializer, consider using an inline-const `const { /* ... */ }` at the usage site instead
12+
= note: `#[warn(interior_mutable_consts)]` on by default
13+
help: for a shared instance of `A`, consider using a `static` item instead
14+
|
15+
LL - const A: Cell<i32> = Cell::new(0);
16+
LL + static A: Cell<i32> = Cell::new(0);
17+
|
18+
help: alternatively consider expecting the lint
19+
|
20+
LL | #[expect(interior_mutable_consts, reason = "...")] const A: Cell<i32> = Cell::new(0);
21+
| ++++++++++++++++++++++++++++++++++++++++++++++++++
22+
23+
warning: interior mutability in `const` items have no effect on the `const` item itself
24+
--> $DIR/interior-mutable-types-in-consts-not-sync.rs:7:1
25+
|
26+
LL | const B: RefCell<i32> = RefCell::new(0);
27+
| ^^^^^^^^^------------^^^^^^^^^^^^^^^^^^^
28+
| |
29+
| `RefCell` is an interior mutable type
30+
|
31+
= note: each usage of a `const` item creates a new temporary
32+
= note: only the temporaries and never the original `const` item `B` will be modified
33+
= help: for use as an initializer, consider using an inline-const `const { /* ... */ }` at the usage site instead
34+
help: for a shared instance of `B`, consider using a `static` item instead
35+
|
36+
LL - const B: RefCell<i32> = RefCell::new(0);
37+
LL + static B: RefCell<i32> = RefCell::new(0);
38+
|
39+
help: alternatively consider expecting the lint
40+
|
41+
LL | #[expect(interior_mutable_consts, reason = "...")] const B: RefCell<i32> = RefCell::new(0);
42+
| ++++++++++++++++++++++++++++++++++++++++++++++++++
43+
44+
warning: interior mutability in `const` items have no effect on the `const` item itself
45+
--> $DIR/interior-mutable-types-in-consts-not-sync.rs:9:1
46+
|
47+
LL | const C: UnsafeCell<i32> = UnsafeCell::new(0);
48+
| ^^^^^^^^^---------------^^^^^^^^^^^^^^^^^^^^^^
49+
| |
50+
| `UnsafeCell` is an interior mutable type
51+
|
52+
= note: each usage of a `const` item creates a new temporary
53+
= note: only the temporaries and never the original `const` item `C` will be modified
54+
= help: for use as an initializer, consider using an inline-const `const { /* ... */ }` at the usage site instead
55+
help: for a shared instance of `C`, consider using a `static` item instead
56+
|
57+
LL - const C: UnsafeCell<i32> = UnsafeCell::new(0);
58+
LL + static C: UnsafeCell<i32> = UnsafeCell::new(0);
59+
|
60+
help: alternatively consider expecting the lint
61+
|
62+
LL | #[expect(interior_mutable_consts, reason = "...")] const C: UnsafeCell<i32> = UnsafeCell::new(0);
63+
| ++++++++++++++++++++++++++++++++++++++++++++++++++
64+
65+
warning: interior mutability in `const` items have no effect on the `const` item itself
66+
--> $DIR/interior-mutable-types-in-consts-not-sync.rs:11:1
67+
|
68+
LL | const D: LazyCell<i32> = LazyCell::new(|| 0);
69+
| ^^^^^^^^^-------------^^^^^^^^^^^^^^^^^^^^^^^
70+
| |
71+
| `LazyCell` is an interior mutable type
72+
|
73+
= note: each usage of a `const` item creates a new temporary
74+
= note: only the temporaries and never the original `const` item `D` will be modified
75+
= help: for use as an initializer, consider using an inline-const `const { /* ... */ }` at the usage site instead
76+
help: for a shared instance of `D`, consider using a `static` item instead
77+
|
78+
LL - const D: LazyCell<i32> = LazyCell::new(|| 0);
79+
LL + static D: LazyCell<i32> = LazyCell::new(|| 0);
80+
|
81+
help: alternatively consider expecting the lint
82+
|
83+
LL | #[expect(interior_mutable_consts, reason = "...")] const D: LazyCell<i32> = LazyCell::new(|| 0);
84+
| ++++++++++++++++++++++++++++++++++++++++++++++++++
85+
86+
warning: interior mutability in `const` items have no effect on the `const` item itself
87+
--> $DIR/interior-mutable-types-in-consts-not-sync.rs:13:1
88+
|
89+
LL | const E: OnceCell<i32> = OnceCell::new();
90+
| ^^^^^^^^^-------------^^^^^^^^^^^^^^^^^^^
91+
| |
92+
| `OnceCell` is an interior mutable type
93+
|
94+
= note: each usage of a `const` item creates a new temporary
95+
= note: only the temporaries and never the original `const` item `E` will be modified
96+
= help: for use as an initializer, consider using an inline-const `const { /* ... */ }` at the usage site instead
97+
help: for a shared instance of `E`, consider using a `static` item instead
98+
|
99+
LL - const E: OnceCell<i32> = OnceCell::new();
100+
LL + static E: OnceCell<i32> = OnceCell::new();
101+
|
102+
help: alternatively consider expecting the lint
103+
|
104+
LL | #[expect(interior_mutable_consts, reason = "...")] const E: OnceCell<i32> = OnceCell::new();
105+
| ++++++++++++++++++++++++++++++++++++++++++++++++++
106+
107+
warning: 5 warnings emitted
108+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//@ check-pass
2+
3+
#![feature(reentrant_lock)]
4+
5+
use std::sync::{Once, Barrier, Condvar, LazyLock, Mutex, OnceLock, ReentrantLock, RwLock};
6+
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicI32, AtomicU32};
7+
8+
const A: Once = Once::new();
9+
//~^ WARN interior mutability in `const` items
10+
const B: Barrier = Barrier::new(0);
11+
//~^ WARN interior mutability in `const` items
12+
const C: Condvar = Condvar::new();
13+
//~^ WARN interior mutability in `const` items
14+
const D: LazyLock<i32> = LazyLock::new(|| 0);
15+
//~^ WARN interior mutability in `const` items
16+
const E: Mutex<i32> = Mutex::new(0);
17+
//~^ WARN interior mutability in `const` items
18+
const F: OnceLock<i32> = OnceLock::new();
19+
//~^ WARN interior mutability in `const` items
20+
const G: ReentrantLock<i32> = ReentrantLock::new(0);
21+
//~^ WARN interior mutability in `const` items
22+
const H: RwLock<i32> = RwLock::new(0);
23+
//~^ WARN interior mutability in `const` items
24+
const I: AtomicBool = AtomicBool::new(false);
25+
//~^ WARN interior mutability in `const` items
26+
const J: AtomicPtr<i32> = AtomicPtr::new(std::ptr::null_mut());
27+
//~^ WARN interior mutability in `const` items
28+
const K: AtomicI32 = AtomicI32::new(0);
29+
//~^ WARN interior mutability in `const` items
30+
const L: AtomicU32 = AtomicU32::new(0);
31+
//~^ WARN interior mutability in `const` items
32+
33+
pub(crate) const X: Once = Once::new();
34+
//~^ WARN interior mutability in `const` items
35+
36+
fn main() {
37+
const Z: Once = Once::new();
38+
//~^ WARN interior mutability in `const` items
39+
}
40+
41+
struct S;
42+
impl S {
43+
const Z: Once = Once::new(); // not a const-item
44+
}

0 commit comments

Comments
 (0)