Skip to content

Commit d670fde

Browse files
committed
Add interior_mutable_consts lint
1 parent 319ab89 commit d670fde

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
@@ -434,6 +434,14 @@ lint_incomplete_include =
434434
435435
lint_inner_macro_attribute_unstable = inner macro attributes are unstable
436436
437+
lint_interior_mutable_consts = interior mutability in `const` items have no effect on the `const` item itself
438+
.label = `{$ty_name}` is an interior mutable type
439+
.temporary = each usage of a `const` item creates a new temporary
440+
.never_original = only the temporaries and never the original `const` item `{$const_name}` will be modified
441+
.suggestion_inline_const = for use as an initializer, consider using an inline-const `const {"{"} /* ... */ {"}"}` at the usage site instead
442+
.suggestion_static = for a shared instance of `{$const_name}`, consider using a `static` item instead
443+
.suggestion_expect = alternatively consider expecting the lint
444+
437445
lint_invalid_asm_label_binary = avoid using labels containing only the digits `0` and `1` in inline assembly
438446
.label = use a different label that doesn't start with `0` or `1`
439447
.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
@@ -1914,6 +1914,55 @@ pub(crate) enum UnpredictableFunctionPointerComparisonsSuggestion<'a, 'tcx> {
19141914
},
19151915
}
19161916

1917+
#[derive(LintDiagnostic)]
1918+
#[diag(lint_interior_mutable_consts)]
1919+
#[note(lint_temporary)]
1920+
#[note(lint_never_original)]
1921+
#[help(lint_suggestion_inline_const)]
1922+
pub(crate) struct InteriorMutableConstsDiag {
1923+
pub ty_name: Ident,
1924+
pub const_name: Ident,
1925+
#[label]
1926+
pub ty_span: Span,
1927+
#[subdiagnostic]
1928+
pub sugg_static: InteriorMutableConstsSuggestionStatic,
1929+
#[subdiagnostic]
1930+
pub sugg_expect: InteriorMutableConstsSuggestionExpect,
1931+
}
1932+
1933+
#[derive(Subdiagnostic)]
1934+
pub(crate) enum InteriorMutableConstsSuggestionStatic {
1935+
#[suggestion(
1936+
lint_suggestion_static,
1937+
code = "{before}static ",
1938+
style = "verbose",
1939+
applicability = "maybe-incorrect"
1940+
)]
1941+
Spanful {
1942+
#[primary_span]
1943+
const_: Span,
1944+
before: &'static str,
1945+
},
1946+
#[help(lint_suggestion_static)]
1947+
Spanless,
1948+
}
1949+
1950+
#[derive(Subdiagnostic)]
1951+
pub(crate) enum InteriorMutableConstsSuggestionExpect {
1952+
#[suggestion(
1953+
lint_suggestion_expect,
1954+
code = "#[expect(interior_mutable_consts, reason = \"...\")] ",
1955+
style = "verbose",
1956+
applicability = "maybe-incorrect"
1957+
)]
1958+
Spanful {
1959+
#[primary_span]
1960+
span: Span,
1961+
},
1962+
#[help(lint_suggestion_expect)]
1963+
Spanless,
1964+
}
1965+
19171966
pub(crate) struct ImproperCTypes<'a> {
19181967
pub ty: Ty<'a>,
19191968
pub desc: &'a str,

compiler/rustc_lint/src/types.rs

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

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

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

219262
impl TypeLimits {
@@ -686,6 +729,48 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
686729
})
687730
}
688731
}
732+
733+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
734+
if let hir::ItemKind::Const(ident, ty, _generics, _body_id) = item.kind
735+
&& let hir::TyKind::Path(ref qpath) = ty.kind
736+
&& let Some(def_id) = cx.qpath_res(qpath, ty.hir_id).opt_def_id()
737+
&& cx.tcx.has_attr(def_id, sym::rustc_significant_interior_mutable_type)
738+
&& let Some(ty_name) = cx.tcx.opt_item_ident(def_id)
739+
{
740+
let (sugg_static, sugg_expect) = if let Some(vis_span) =
741+
item.vis_span.find_ancestor_inside(item.span)
742+
&& item.span.can_be_used_for_suggestions()
743+
&& vis_span.can_be_used_for_suggestions()
744+
{
745+
(
746+
InteriorMutableConstsSuggestionStatic::Spanful {
747+
const_: item.vis_span.between(ident.span),
748+
before: if !vis_span.is_empty() { " " } else { "" },
749+
},
750+
InteriorMutableConstsSuggestionExpect::Spanful {
751+
span: item.span.shrink_to_lo(),
752+
},
753+
)
754+
} else {
755+
(
756+
InteriorMutableConstsSuggestionStatic::Spanless,
757+
InteriorMutableConstsSuggestionExpect::Spanless,
758+
)
759+
};
760+
761+
cx.emit_span_lint(
762+
INTERIOR_MUTABLE_CONSTS,
763+
item.span,
764+
InteriorMutableConstsDiag {
765+
ty_name,
766+
ty_span: ty.span,
767+
const_name: ident,
768+
sugg_static,
769+
sugg_expect,
770+
},
771+
);
772+
}
773+
}
689774
}
690775

691776
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)