|
1 | 1 | use clippy_config::msrvs::{self, Msrv}; |
2 | 2 | use clippy_utils::diagnostics::span_lint_and_sugg; |
| 3 | +use clippy_utils::macros::macro_backtrace; |
3 | 4 | use clippy_utils::qualify_min_const_fn::is_min_const_fn; |
4 | 5 | use clippy_utils::source::snippet; |
5 | 6 | use clippy_utils::{fn_has_unsatisfiable_preds, peel_blocks}; |
6 | 7 | use rustc_errors::Applicability; |
7 | | -use rustc_hir::{intravisit, ExprKind}; |
| 8 | +use rustc_hir::{intravisit, Expr, ExprKind}; |
8 | 9 | use rustc_lint::{LateContext, LateLintPass}; |
9 | 10 | use rustc_session::impl_lint_pass; |
10 | | -use rustc_span::sym::thread_local_macro; |
| 11 | +use rustc_span::sym::{self, thread_local_macro}; |
11 | 12 |
|
12 | 13 | declare_clippy_lint! { |
13 | 14 | /// ### What it does |
@@ -69,6 +70,26 @@ fn is_thread_local_initializer( |
69 | 70 | ) |
70 | 71 | } |
71 | 72 |
|
| 73 | +fn is_unreachable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| 74 | + if let Some(macro_call) = macro_backtrace(expr.span).next() |
| 75 | + && let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) |
| 76 | + { |
| 77 | + return (matches!( |
| 78 | + diag_name, |
| 79 | + sym::core_panic_macro |
| 80 | + | sym::std_panic_macro |
| 81 | + | sym::core_panic_2015_macro |
| 82 | + | sym::std_panic_2015_macro |
| 83 | + | sym::core_panic_2021_macro |
| 84 | + ) && !cx.tcx.hir().is_inside_const_context(expr.hir_id)) |
| 85 | + || matches!( |
| 86 | + diag_name, |
| 87 | + sym::unimplemented_macro | sym::todo_macro | sym::unreachable_macro | sym::unreachable_2015_macro |
| 88 | + ); |
| 89 | + } |
| 90 | + false |
| 91 | +} |
| 92 | + |
72 | 93 | #[inline] |
73 | 94 | fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id::DefId, msrv: &Msrv) -> bool { |
74 | 95 | // Building MIR for `fn`s with unsatisfiable preds results in ICE. |
@@ -102,12 +123,17 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst { |
102 | 123 | // for details on this issue, see: |
103 | 124 | // https://github.com/rust-lang/rust-clippy/pull/12276 |
104 | 125 | && !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. |
108 | 126 | && let ExprKind::Block(block, _) = body.value.kind |
109 | 127 | && let Some(unpeeled) = block.expr |
110 | 128 | && let ret_expr = peel_blocks(unpeeled) |
| 129 | + // A common pattern around threadlocal! is to make the value unreachable |
| 130 | + // to force an initialization before usage |
| 131 | + // https://github.com/rust-lang/rust-clippy/issues/12637 |
| 132 | + // we ensure that this is reachable before we check in mir |
| 133 | + && !is_unreachable(cx, ret_expr) |
| 134 | + && initializer_can_be_made_const(cx, defid, &self.msrv) |
| 135 | + // we know that the function is const-qualifiable, so now |
| 136 | + // we need only to get the initializer expression to span-lint it. |
111 | 137 | && let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }") |
112 | 138 | && initializer_snippet != "thread_local! { ... }" |
113 | 139 | { |
|
0 commit comments