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