pin_init_internal/
diagnostics.rs1use std::cell::RefCell;
4use std::fmt::Display;
5use std::marker::PhantomData;
6
7use proc_macro2::{Span, TokenStream};
8use quote::{quote, quote_spanned};
9use syn::{spanned::Spanned, Error};
10
11pub(crate) struct DiagCtxt(PhantomData<*mut ()>);
12pub(crate) struct ErrorGuaranteed(());
13
14struct DiagCtxtData {
15 diag: TokenStream,
16}
17
18thread_local! {
19 static DIAGNOSTICS: RefCell<Option<DiagCtxtData>> = const { RefCell::new(None) };
20}
21
22impl From<syn::Error> for ErrorGuaranteed {
24 fn from(error: syn::Error) -> Self {
25 DIAGNOSTICS.with_borrow_mut(|data| {
26 data.as_mut()
27 .unwrap()
28 .diag
29 .extend(error.into_compile_error());
30 });
31 Self(())
32 }
33}
34
35impl DiagCtxt {
36 pub(crate) fn error(&self, span: impl Spanned, msg: impl Display) -> ErrorGuaranteed {
37 Error::new(span.span(), msg).into()
38 }
39
40 pub(crate) fn warn(&self, span: impl Spanned, msg: impl Display) {
41 let msg = format!("\n{}", msg);
43 DIAGNOSTICS.with_borrow_mut(|data| {
44 data.as_mut()
45 .unwrap()
46 .diag
47 .extend(quote_spanned!(span.span() =>
48 const _: () = {
51 #[deprecated = #msg]
52 const fn warn() {}
53 warn();
54 };
55 ))
56 });
57 }
58
59 pub(crate) fn current<R>(f: impl FnOnce(&DiagCtxt) -> R) -> R {
61 DIAGNOSTICS.with_borrow(|data| {
62 assert!(data.is_some(), "No active `DiagCtxt`");
63 });
64
65 f(&DiagCtxt(PhantomData))
66 }
67
68 fn with(
69 f: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
70 merge_diag: impl FnOnce(TokenStream, TokenStream) -> TokenStream,
71 convert_diag: impl FnOnce(TokenStream) -> TokenStream,
72 ) -> TokenStream {
73 DIAGNOSTICS.with_borrow_mut(|data| {
74 assert!(data.is_none(), "`DiagCtxt` cannot be nested");
75 *data = Some(DiagCtxtData {
76 diag: TokenStream::new(),
77 });
78 });
79
80 let mut dcx = DiagCtxt(PhantomData);
81 let result = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut dcx))) {
82 Ok(result) => result,
83 Err(payload) => {
84 let message = if let Some(&s) = payload.downcast_ref::<&'static str>() {
88 s
89 } else if let Some(s) = payload.downcast_ref::<String>() {
90 s.as_str()
91 } else {
92 "Box<dyn Any>"
93 };
94
95 Err(dcx.error(
96 Span::mixed_site(),
97 format!("proc macro panicked: {message}"),
98 ))
99 }
100 };
101
102 let data = DIAGNOSTICS.with_borrow_mut(|data| data.take().unwrap());
103
104 match result {
105 Ok(stream) => {
106 if data.diag.is_empty() {
107 stream
108 } else {
109 merge_diag(stream, data.diag)
110 }
111 }
112 Err(ErrorGuaranteed(())) => convert_diag(data.diag),
113 }
114 }
115
116 pub(crate) fn for_item(
117 f: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
118 ) -> TokenStream {
119 Self::with(
120 f,
121 |mut out, diag| {
122 out.extend(diag);
123 out
124 },
125 std::convert::identity,
126 )
127 }
128
129 pub(crate) fn for_expr(
130 f: impl FnOnce(&mut DiagCtxt) -> Result<TokenStream, ErrorGuaranteed>,
131 ) -> TokenStream {
132 Self::with(
133 f,
134 |out, diag| {
135 quote!({
138 #diag
139 #out
140 })
141 },
142 |diag| {
143 quote!({
144 #diag
145 })
146 },
147 )
148 }
149}