Skip to main content

pin_init_internal/
diagnostics.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use 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
22// Allows `syn::Error` to be emitted into the current diagnostic context with just `?`.
23impl 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        // Have the message start on a new line for visual clarity.
42        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                    // Approximate using deprecated warning while `proc_macro_diagnostic` is
49                    // unstable.
50                    const _: () = {
51                        #[deprecated = #msg]
52                        const fn warn() {}
53                        warn();
54                    };
55                ))
56        });
57    }
58
59    /// Execute the provided function with the current diagnostic context.
60    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                // Robustness against panicking in macros.
85                //
86                // Ensure that any error messages are still emitted when this happens.
87                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                // Diagnostics that we generate are always items.
136                // So for expressions create a block to place diagnostics in item position.
137                quote!({
138                    #diag
139                    #out
140                })
141            },
142            |diag| {
143                quote!({
144                    #diag
145                })
146            },
147        )
148    }
149}