xref: /linux/rust/pin-init/internal/src/diagnostics.rs (revision bb29b957eaaa15be33243550d9ee576c2d601669)
1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2 
3 use std::cell::RefCell;
4 use std::fmt::Display;
5 use std::marker::PhantomData;
6 
7 use proc_macro2::{Span, TokenStream};
8 use quote::{quote, quote_spanned};
9 use syn::{spanned::Spanned, Error};
10 
11 pub(crate) struct DiagCtxt(PhantomData<*mut ()>);
12 pub(crate) struct ErrorGuaranteed(());
13 
14 struct DiagCtxtData {
15     diag: TokenStream,
16 }
17 
18 thread_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 `?`.
23 impl 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 
35 impl 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 }
150