Skip to content

Commit 9580952

Browse files
Threadlocal_initializer_can_be_made_const will not trigger for unreachable initializers
This commit introduces a check to ensure that the lint won't trigger when the initializer is unreachable, such as: ``` thread_local! { static STATE: Cell<usize> = panic!(); } ``` This is achieved by looking at the unpeeled initializer expression and ensuring that the parent macro is not `panic!()`, `todo!()`, `unreachable!()`, `unimplemented!()`. fixes #12637 changelog: [`threadlocal_initializer_can_be_made_const`] will no longer trigger on `unreachable` macros.
1 parent cdd6336 commit 9580952

4 files changed

+90
-10
lines changed

clippy_lints/src/thread_local_initializer_can_be_made_const.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
44
use clippy_utils::source::snippet;
55
use clippy_utils::{fn_has_unsatisfiable_preds, peel_blocks};
6+
use clippy_utils::macros::{is_panic, macro_backtrace};
67
use rustc_errors::Applicability;
7-
use rustc_hir::{intravisit, ExprKind};
8+
use rustc_hir::{intravisit, ExprKind, Expr};
89
use rustc_lint::{LateContext, LateLintPass};
910
use rustc_session::impl_lint_pass;
1011
use rustc_span::sym::thread_local_macro;
@@ -69,6 +70,16 @@ fn is_thread_local_initializer(
6970
)
7071
}
7172

73+
fn is_unreachable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
74+
let Some(macro_call) = macro_backtrace(expr.span).next() else {
75+
return false;
76+
};
77+
if is_panic(cx, macro_call.def_id) {
78+
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
79+
}
80+
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "todo" | "unimplemented" | "unreachable")
81+
}
82+
7283
#[inline]
7384
fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id::DefId, msrv: &Msrv) -> bool {
7485
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
@@ -102,12 +113,17 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
102113
// for details on this issue, see:
103114
// https://github.com/rust-lang/rust-clippy/pull/12276
104115
&& !cx.tcx.is_const_fn(defid)
105-
&& initializer_can_be_made_const(cx, defid, &self.msrv)
106-
// we know that the function is const-qualifiable, so now
107-
// we need only to get the initializer expression to span-lint it.
108116
&& let ExprKind::Block(block, _) = body.value.kind
109117
&& let Some(unpeeled) = block.expr
110118
&& let ret_expr = peel_blocks(unpeeled)
119+
// A common pattern around threadlocal! is to make the value unreachable
120+
// to force an initialization before usage
121+
// https://github.com/rust-lang/rust-clippy/issues/12637
122+
// we ensure that this is reachable before we check in mir
123+
&& !is_unreachable(cx, ret_expr)
124+
&& initializer_can_be_made_const(cx, defid, &self.msrv)
125+
// we know that the function is const-qualifiable, so now
126+
// we need only to get the initializer expression to span-lint it.
111127
&& let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }")
112128
&& initializer_snippet != "thread_local! { ... }"
113129
{

tests/ui/thread_local_initializer_can_be_made_const.fixed

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![warn(clippy::thread_local_initializer_can_be_made_const)]
22

33
use std::cell::RefCell;
4+
use std::cell::Cell;
45

56
fn main() {
67
// lint and suggest const
@@ -36,6 +37,37 @@ fn main() {
3637
}
3738
}
3839

40+
41+
fn issue_12637() {
42+
/// The set methods on LocalKey<Cell<T>> and LocalKey<RefCell<T>> are
43+
/// guaranteed to bypass the thread_local's initialization expression.
44+
/// See rust-lang/rust#92122. Thus, = panic!() is a useful idiom for
45+
/// forcing the use of set on each thread before it accesses the thread local in any other manner.
46+
thread_local! {
47+
static STATE_12637_PANIC: Cell<usize> = panic!();
48+
}
49+
STATE_12637_PANIC.set(9);
50+
println!("{}", STATE_12637_PANIC.get());
51+
52+
thread_local! {
53+
static STATE_12637_TODO: Cell<usize> = todo!();
54+
}
55+
STATE_12637_TODO.set(9);
56+
println!("{}", STATE_12637_TODO.get());
57+
58+
thread_local! {
59+
static STATE_12637_UNIMPLEMENTED: Cell<usize> = unimplemented!();
60+
}
61+
STATE_12637_UNIMPLEMENTED.set(9);
62+
println!("{}", STATE_12637_UNIMPLEMENTED.get());
63+
64+
thread_local! {
65+
static STATE_12637_UNREACHABLE: Cell<usize> = unreachable!();
66+
}
67+
STATE_12637_UNREACHABLE.set(9);
68+
println!("{}", STATE_12637_UNREACHABLE.get());
69+
}
70+
3971
#[clippy::msrv = "1.58"]
4072
fn f() {
4173
thread_local! {

tests/ui/thread_local_initializer_can_be_made_const.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![warn(clippy::thread_local_initializer_can_be_made_const)]
22

33
use std::cell::RefCell;
4+
use std::cell::Cell;
45

56
fn main() {
67
// lint and suggest const
@@ -36,6 +37,37 @@ fn main() {
3637
}
3738
}
3839

40+
41+
fn issue_12637() {
42+
/// The set methods on LocalKey<Cell<T>> and LocalKey<RefCell<T>> are
43+
/// guaranteed to bypass the thread_local's initialization expression.
44+
/// See rust-lang/rust#92122. Thus, = panic!() is a useful idiom for
45+
/// forcing the use of set on each thread before it accesses the thread local in any other manner.
46+
thread_local! {
47+
static STATE_12637_PANIC: Cell<usize> = panic!();
48+
}
49+
STATE_12637_PANIC.set(9);
50+
println!("{}", STATE_12637_PANIC.get());
51+
52+
thread_local! {
53+
static STATE_12637_TODO: Cell<usize> = todo!();
54+
}
55+
STATE_12637_TODO.set(9);
56+
println!("{}", STATE_12637_TODO.get());
57+
58+
thread_local! {
59+
static STATE_12637_UNIMPLEMENTED: Cell<usize> = unimplemented!();
60+
}
61+
STATE_12637_UNIMPLEMENTED.set(9);
62+
println!("{}", STATE_12637_UNIMPLEMENTED.get());
63+
64+
thread_local! {
65+
static STATE_12637_UNREACHABLE: Cell<usize> = unreachable!();
66+
}
67+
STATE_12637_UNREACHABLE.set(9);
68+
println!("{}", STATE_12637_UNREACHABLE.get());
69+
}
70+
3971
#[clippy::msrv = "1.58"]
4072
fn f() {
4173
thread_local! {

tests/ui/thread_local_initializer_can_be_made_const.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: initializer for `thread_local` value can be made `const`
2-
--> tests/ui/thread_local_initializer_can_be_made_const.rs:8:41
2+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:9:41
33
|
44
LL | static BUF_1: RefCell<String> = RefCell::new(String::new());
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
@@ -8,31 +8,31 @@ LL | static BUF_1: RefCell<String> = RefCell::new(String::new());
88
= help: to override `-D warnings` add `#[allow(clippy::thread_local_initializer_can_be_made_const)]`
99

1010
error: initializer for `thread_local` value can be made `const`
11-
--> tests/ui/thread_local_initializer_can_be_made_const.rs:18:29
11+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:19:29
1212
|
1313
LL | static SIMPLE:i32 = 1;
1414
| ^ help: replace with: `const { 1 }`
1515

1616
error: initializer for `thread_local` value can be made `const`
17-
--> tests/ui/thread_local_initializer_can_be_made_const.rs:24:59
17+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:25:59
1818
|
1919
LL | static BUF_3_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
2121

2222
error: initializer for `thread_local` value can be made `const`
23-
--> tests/ui/thread_local_initializer_can_be_made_const.rs:26:59
23+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:27:59
2424
|
2525
LL | static BUF_4_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
2626
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
2727

2828
error: initializer for `thread_local` value can be made `const`
29-
--> tests/ui/thread_local_initializer_can_be_made_const.rs:32:31
29+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:33:31
3030
|
3131
LL | static PEEL_ME: i32 = { 1 };
3232
| ^^^^^ help: replace with: `const { 1 }`
3333

3434
error: initializer for `thread_local` value can be made `const`
35-
--> tests/ui/thread_local_initializer_can_be_made_const.rs:34:36
35+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:35:36
3636
|
3737
LL | static PEEL_ME_MANY: i32 = { let x = 1; x * x };
3838
| ^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { { let x = 1; x * x } }`

0 commit comments

Comments
 (0)