Skip to content

Commit e73bb00

Browse files
committed
Auto merge of #12026 - PartiallyTyped:12015, r=llogiq
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. If so, we lint that the initializer value can be made const. changelog: new lint [`thread_local_initializer_can_be_made_const`] fixes: #12015
2 parents e1dbafd + 70024e1 commit e73bb00

7 files changed

+197
-0
lines changed

Diff for: CHANGELOG.md

+1
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

Diff for: clippy_lints/src/declared_lints.rs

+1
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,

Diff for: clippy_lints/src/lib.rs

+4
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

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+
}
+30
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+
}
+30
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+
}
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)