From 11de921e659ec2096bb3f77a969bc7d8223cbb93 Mon Sep 17 00:00:00 2001 From: Mohammed Alkindi Date: Sat, 15 Aug 2026 18:04:57 +0400 Subject: [PATCH 1/2] Fix missing_const_for_thread_local false positive on os-TLS targets On targets without native #[thread_local], std's thread_local! now expands a const initializer into a plain non-const init fn, so the is_const_fn guard from #12276 no longer filters it and the lint fires on initializers that are already const. Skip ExprKind::ConstBlock initializers: for those there is never anything to suggest. Fixes #17566 --- clippy_lints/src/missing_const_for_thread_local.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clippy_lints/src/missing_const_for_thread_local.rs b/clippy_lints/src/missing_const_for_thread_local.rs index 0c0548029f5e..08056ae5b2ff 100644 --- a/clippy_lints/src/missing_const_for_thread_local.rs +++ b/clippy_lints/src/missing_const_for_thread_local.rs @@ -122,6 +122,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal { && let ExprKind::Block(block, _) = body.value.kind && let Some(unpeeled) = block.expr && let ret_expr = peel_blocks(unpeeled) + // The initializer is already a `const` block: there is nothing to suggest. + // On targets without `#[thread_local]` (e.g. x86_64-pc-windows-gnu), the + // generated init fn is not a `const fn` even for `const` initializers, + // so the `is_const_fn` check above does not cover this case. + && !matches!(ret_expr.kind, ExprKind::ConstBlock(_)) // A common pattern around threadlocal! is to make the value unreachable // to force an initialization before usage // https://github.com/rust-lang/rust-clippy/issues/12637 From 43672c56244a28c65d8fedabaf27d5a52ecfe459 Mon Sep 17 00:00:00 2001 From: Mohammed Alkindi Date: Mon, 24 Aug 2026 14:29:26 +0400 Subject: [PATCH 2/2] test: add the issue 17566 reproducer for the const-block initializer --- tests/ui/missing_const_for_thread_local.fixed | 8 ++++++++ tests/ui/missing_const_for_thread_local.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/ui/missing_const_for_thread_local.fixed b/tests/ui/missing_const_for_thread_local.fixed index 2efe10c153da..8480d81dccd8 100644 --- a/tests/ui/missing_const_for_thread_local.fixed +++ b/tests/ui/missing_const_for_thread_local.fixed @@ -69,6 +69,14 @@ fn issue_12637() { println!("{}", STATE_12637_UNREACHABLE.get()); } +fn issue_17566() { + // On targets without native `#[thread_local]`, a `const` initializer expands to a + // non-`const` init fn, which used to slip past the `is_const_fn` guard and lint here. + thread_local! { + static STATE_17566: Cell> = const { Cell::new(None) }; + } +} + #[clippy::msrv = "1.58"] fn f() { thread_local! { diff --git a/tests/ui/missing_const_for_thread_local.rs b/tests/ui/missing_const_for_thread_local.rs index 119cc5a99fa0..4ea1f80867a1 100644 --- a/tests/ui/missing_const_for_thread_local.rs +++ b/tests/ui/missing_const_for_thread_local.rs @@ -69,6 +69,14 @@ fn issue_12637() { println!("{}", STATE_12637_UNREACHABLE.get()); } +fn issue_17566() { + // On targets without native `#[thread_local]`, a `const` initializer expands to a + // non-`const` init fn, which used to slip past the `is_const_fn` guard and lint here. + thread_local! { + static STATE_17566: Cell> = const { Cell::new(None) }; + } +} + #[clippy::msrv = "1.58"] fn f() { thread_local! {