1extern crate proc_macro;
4
5use std::hash::{Hash, Hasher};
6
7use proc_macro2::{Ident, Literal, Span, TokenStream};
8use quote::quote_spanned;
9use serde::{Deserialize, Serialize};
10
11use crate::pretty_span::{PrettySpan, make_source_path_relative};
12
13#[non_exhaustive]
15#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub enum Level {
17 Error,
21 Warning,
25 Note,
29 Help,
33}
34impl Level {
35 pub fn is_error(&self) -> bool {
37 self <= &Self::Error
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Diagnostic<S = Span> {
48 pub span: S,
50 pub level: Level,
52 pub message: String,
54}
55impl<S> Diagnostic<S> {
56 pub fn is_error(&self) -> bool {
58 self.level.is_error()
59 }
60}
61impl Diagnostic {
62 pub fn spanned(span: Span, level: Level, message: impl Into<String>) -> Self {
64 let message = message.into();
65 Self {
66 span,
67 level,
68 message,
69 }
70 }
71
72 pub fn try_emit(&self) -> Result<(), TokenStream> {
75 #[cfg(nightly)]
76 if proc_macro::is_available() {
77 let pm_diag = match self.level {
78 Level::Error => self.span.unwrap().error(&*self.message),
79 Level::Warning => self.span.unwrap().warning(&*self.message),
80 Level::Note => self.span.unwrap().note(&*self.message),
81 Level::Help => self.span.unwrap().help(&*self.message),
82 };
83 pm_diag.emit();
84 return Ok(());
85 }
86 Err(self.to_tokens())
87 }
88
89 pub fn try_emit_all<'a>(
92 diagnostics: impl IntoIterator<Item = &'a Self>,
93 ) -> Result<(), TokenStream> {
94 if let Some(tokens) = diagnostics
95 .into_iter()
96 .filter_map(|diag| diag.try_emit().err())
97 .reduce(|mut tokens, next| {
98 tokens.extend(next);
99 tokens
100 })
101 {
102 Err(tokens)
103 } else {
104 Ok(())
105 }
106 }
107
108 pub fn to_tokens(&self) -> TokenStream {
111 let msg_lit: Literal = Literal::string(&self.message);
112 let unique_ident = {
113 let mut hasher = std::collections::hash_map::DefaultHasher::new();
114 self.level.hash(&mut hasher);
115 self.message.hash(&mut hasher);
116 let hash = hasher.finish();
117 Ident::new(&format!("diagnostic_{}", hash), self.span)
118 };
119
120 if Level::Error == self.level {
121 quote_spanned! {self.span=>
122 {
123 ::core::compile_error!(#msg_lit);
124 }
125 }
126 } else {
127 let level_ident = Ident::new(&format!("{:?}", self.level), self.span);
129 quote_spanned! {self.span=>
130 {
131 #[allow(dead_code, non_snake_case)]
132 fn #unique_ident() {
133 #[deprecated = #msg_lit]
134 struct #level_ident {}
135 #[warn(deprecated)]
136 #level_ident {};
137 }
138 }
139 }
140 }
141 }
142
143 pub fn to_serde(&self) -> Diagnostic<SerdeSpan> {
147 let Self {
148 span,
149 level,
150 message,
151 } = self;
152 Diagnostic {
153 span: (*span).into(),
154 level: *level,
155 message: message.clone(),
156 }
157 }
158}
159impl From<syn::Error> for Diagnostic {
160 fn from(value: syn::Error) -> Self {
161 Self::spanned(value.span(), Level::Error, value.to_string())
162 }
163}
164impl std::fmt::Display for Diagnostic {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 writeln!(f, "{:?}: {}", self.level, self.message)?;
167 write!(f, " --> {}", PrettySpan(self.span))?;
168 Ok(())
169 }
170}
171impl std::fmt::Display for Diagnostic<SerdeSpan> {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 writeln!(f, "{:?}: {}", self.level, self.message)?;
174 write!(f, " --> {}", self.span)?;
175 Ok(())
176 }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct SerdeSpan {
183 pub file: Option<String>,
185 pub line: usize,
187 pub column: usize,
189}
190impl From<Span> for SerdeSpan {
191 fn from(span: Span) -> Self {
192 #[cfg_attr(
193 not(nightly),
194 expect(unused_labels, reason = "conditional compilation")
195 )]
196 let file = 'a: {
197 #[cfg(nightly)]
198 if proc_macro::is_available() {
199 break 'a Some(span.unwrap().file());
200 }
201
202 None
203 };
204
205 Self {
206 file,
207 line: span.start().line,
208 column: span.start().column,
209 }
210 }
211}
212impl std::fmt::Display for SerdeSpan {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 write!(
215 f,
216 "{}:{}:{}",
217 self.file
218 .as_ref()
219 .map(make_source_path_relative)
220 .map(|path| path.display().to_string())
221 .as_deref()
222 .unwrap_or("unknown"),
223 self.line,
224 self.column
225 )
226 }
227}