Skip to content

Commit 70024e1

Browse files
New Lint: [thread_local_initializer_can_be_made_const]
Adds a new lint to suggest using `const` on `thread_local!` initializers that can be evaluated at compile time. Impl details: The lint relies on the expansion of `thread_local!`. For non const-labelled initializers, `thread_local!` produces a function called `__init` that lazily initializes the value. We check the function and decide whether the body can be const. The body of the function is exactly the initializer. If so, we lint the body. changelog: new lint [`thread_local_initializer_can_be_made_const`]
1 parent e1dbafd commit 70024e1

7 files changed

+197
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5616,6 +5616,7 @@ Released 2018-09-13
56165616
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
56175617
[`test_attr_in_doctest`]: https://rust-lang.github.io/rust-clippy/master/index.html#test_attr_in_doctest
56185618
[`tests_outside_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#tests_outside_test_module
5619+
[`thread_local_initializer_can_be_made_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#thread_local_initializer_can_be_made_const
56195620
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
56205621
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
56215622
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
652652
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
653653
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
654654
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
655+
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
655656
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
656657
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
657658
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ mod swap_ptr_to_ref;
323323
mod tabs_in_doc_comments;
324324
mod temporary_assignment;
325325
mod tests_outside_test_module;
326+
mod thread_local_initializer_can_be_made_const;
326327
mod to_digit_is_some;
327328
mod trailing_empty_array;
328329
mod trait_bounds;
@@ -1088,6 +1089,9 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10881089
behavior: pub_underscore_fields_behavior,
10891090
})
10901091
});
1092+
store.register_late_pass(move |_| {
1093+
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
1094+
});
10911095
// add lints here, do not remove this comment, it's used in `new_lint`
10921096
}
10931097

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use clippy_config::msrvs::Msrv;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::fn_has_unsatisfiable_preds;
4+
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
5+
use clippy_utils::source::snippet;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{intravisit, ExprKind};
8+
use rustc_lint::{LateContext, LateLintPass, LintContext};
9+
use rustc_middle::lint::in_external_macro;
10+
use rustc_session::impl_lint_pass;
11+
use rustc_span::sym::thread_local_macro;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Suggests to use `const` in `thread_local!` macro if possible.
16+
/// ### Why is this bad?
17+
///
18+
/// The `thread_local!` macro wraps static declarations and makes them thread-local.
19+
/// It supports using a `const` keyword that may be used for declarations that can
20+
/// be evaluated as a constant expression. This can enable a more efficient thread
21+
/// local implementation that can avoid lazy initialization. For types that do not
22+
/// need to be dropped, this can enable an even more efficient implementation that
23+
/// does not need to track any additional state.
24+
///
25+
/// https://doc.rust-lang.org/std/macro.thread_local.html
26+
///
27+
/// ### Example
28+
/// ```no_run
29+
/// // example code where clippy issues a warning
30+
/// thread_local! {
31+
/// static BUF: String = String::new();
32+
/// }
33+
/// ```
34+
/// Use instead:
35+
/// ```no_run
36+
/// // example code which does not raise clippy warning
37+
/// thread_local! {
38+
/// static BUF: String = const { String::new() };
39+
/// }
40+
/// ```
41+
#[clippy::version = "1.76.0"]
42+
pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
43+
perf,
44+
"suggest using `const` in `thread_local!` macro"
45+
}
46+
47+
pub struct ThreadLocalInitializerCanBeMadeConst {
48+
msrv: Msrv,
49+
}
50+
51+
impl ThreadLocalInitializerCanBeMadeConst {
52+
#[must_use]
53+
pub fn new(msrv: Msrv) -> Self {
54+
Self { msrv }
55+
}
56+
}
57+
58+
impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]);
59+
60+
impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
61+
fn check_fn(
62+
&mut self,
63+
cx: &LateContext<'tcx>,
64+
fn_kind: rustc_hir::intravisit::FnKind<'tcx>,
65+
_: &'tcx rustc_hir::FnDecl<'tcx>,
66+
body: &'tcx rustc_hir::Body<'tcx>,
67+
span: rustc_span::Span,
68+
defid: rustc_span::def_id::LocalDefId,
69+
) {
70+
if in_external_macro(cx.sess(), span)
71+
&& let Some(callee) = span.source_callee()
72+
&& let Some(macro_def_id) = callee.macro_def_id
73+
&& cx.tcx.is_diagnostic_item(thread_local_macro, macro_def_id)
74+
&& let intravisit::FnKind::ItemFn(..) = fn_kind
75+
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
76+
&& !fn_has_unsatisfiable_preds(cx, defid.to_def_id())
77+
&& let mir = cx.tcx.optimized_mir(defid.to_def_id())
78+
&& let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv)
79+
// this is the `__init` function emitted by the `thread_local!` macro
80+
// when the `const` keyword is not used. We avoid checking the `__init` directly
81+
// as that is not a public API.
82+
// we know that the function is const-qualifiable, so now we need only to get the
83+
// initializer expression to span-lint it.
84+
&& let ExprKind::Block(block, _) = body.value.kind
85+
&& let Some(ret_expr) = block.expr
86+
&& let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }")
87+
&& initializer_snippet != "thread_local! { ... }"
88+
{
89+
span_lint_and_sugg(
90+
cx,
91+
THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
92+
ret_expr.span,
93+
"initializer for `thread_local` value can be made `const`",
94+
"replace with",
95+
format!("const {{ {initializer_snippet} }}"),
96+
Applicability::MachineApplicable,
97+
);
98+
}
99+
}
100+
101+
extract_msrv_attr!(LateContext);
102+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#![warn(clippy::thread_local_initializer_can_be_made_const)]
2+
3+
use std::cell::RefCell;
4+
5+
fn main() {
6+
// lint and suggest const
7+
thread_local! {
8+
static BUF_1: RefCell<String> = const { RefCell::new(String::new()) };
9+
}
10+
//~^^ ERROR: initializer for `thread_local` value can be made `const`
11+
12+
// don't lint
13+
thread_local! {
14+
static BUF_2: RefCell<String> = const { RefCell::new(String::new()) };
15+
}
16+
17+
thread_local! {
18+
static SIMPLE:i32 = const { 1 };
19+
}
20+
//~^^ ERROR: initializer for `thread_local` value can be made `const`
21+
22+
// lint and suggest const for all non const items
23+
thread_local! {
24+
static BUF_3_CAN_BE_MADE_CONST: RefCell<String> = const { RefCell::new(String::new()) };
25+
static CONST_MIXED_WITH:i32 = const { 1 };
26+
static BUF_4_CAN_BE_MADE_CONST: RefCell<String> = const { RefCell::new(String::new()) };
27+
}
28+
//~^^^^ ERROR: initializer for `thread_local` value can be made `const`
29+
//~^^^ ERROR: initializer for `thread_local` value can be made `const`
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#![warn(clippy::thread_local_initializer_can_be_made_const)]
2+
3+
use std::cell::RefCell;
4+
5+
fn main() {
6+
// lint and suggest const
7+
thread_local! {
8+
static BUF_1: RefCell<String> = RefCell::new(String::new());
9+
}
10+
//~^^ ERROR: initializer for `thread_local` value can be made `const`
11+
12+
// don't lint
13+
thread_local! {
14+
static BUF_2: RefCell<String> = const { RefCell::new(String::new()) };
15+
}
16+
17+
thread_local! {
18+
static SIMPLE:i32 = 1;
19+
}
20+
//~^^ ERROR: initializer for `thread_local` value can be made `const`
21+
22+
// lint and suggest const for all non const items
23+
thread_local! {
24+
static BUF_3_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
25+
static CONST_MIXED_WITH:i32 = const { 1 };
26+
static BUF_4_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
27+
}
28+
//~^^^^ ERROR: initializer for `thread_local` value can be made `const`
29+
//~^^^ ERROR: initializer for `thread_local` value can be made `const`
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error: initializer for `thread_local` value can be made `const`
2+
--> $DIR/thread_local_initializer_can_be_made_const.rs:8:41
3+
|
4+
LL | static BUF_1: RefCell<String> = RefCell::new(String::new());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
6+
|
7+
= note: `-D clippy::thread-local-initializer-can-be-made-const` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::thread_local_initializer_can_be_made_const)]`
9+
10+
error: initializer for `thread_local` value can be made `const`
11+
--> $DIR/thread_local_initializer_can_be_made_const.rs:18:29
12+
|
13+
LL | static SIMPLE:i32 = 1;
14+
| ^ help: replace with: `const { 1 }`
15+
16+
error: initializer for `thread_local` value can be made `const`
17+
--> $DIR/thread_local_initializer_can_be_made_const.rs:24:59
18+
|
19+
LL | static BUF_3_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
21+
22+
error: initializer for `thread_local` value can be made `const`
23+
--> $DIR/thread_local_initializer_can_be_made_const.rs:26:59
24+
|
25+
LL | static BUF_4_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
26+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
27+
28+
error: aborting due to 4 previous errors
29+

0 commit comments

Comments
 (0)