zyn_core/mark/
diagnostic.rs1use proc_macro2::Span;
2use proc_macro2::TokenStream;
3
4use crate::mark::Level;
5use crate::mark::MultiSpan;
6
7#[derive(Debug, Clone, Default)]
12pub struct Diagnostic {
13 level: Level,
14 message: String,
15 spans: Vec<Span>,
16 children: Vec<Diagnostic>,
17}
18
19pub type Result<T> = std::result::Result<T, Diagnostic>;
21
22impl Diagnostic {
23 pub fn span(&self) -> Span {
25 let mut value = self.spans.first().copied().unwrap_or_else(Span::call_site);
26
27 for item in &self.spans {
28 value = value.join(*item).unwrap_or_else(Span::call_site);
29 }
30
31 value
32 }
33
34 pub fn level(&self) -> Level {
36 self.children
37 .iter()
38 .map(|d| d.level)
39 .max()
40 .unwrap_or(self.level)
41 .max(self.level)
42 }
43
44 pub fn iter(&self) -> impl Iterator<Item = &Self> {
45 self.children.iter()
46 }
47
48 pub fn walk(&self) -> Walk<'_> {
49 Walk {
50 stack: self.children.iter().rev().collect(),
51 }
52 }
53
54 pub fn is_error(&self) -> bool {
56 self.level() >= Level::Error
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.level == Level::None && self.children.is_empty()
62 }
63
64 pub fn len(&self) -> usize {
66 self.children.len()
67 }
68
69 #[cfg(feature = "diagnostics")]
72 pub fn emit_as_item_tokens(self) -> TokenStream {
73 let diag: proc_macro2_diagnostics::Diagnostic = self.into();
74 diag.emit_as_item_tokens()
75 }
76
77 #[cfg(not(feature = "diagnostics"))]
78 pub fn emit_as_item_tokens(self) -> TokenStream {
79 self.emit_fallback()
80 }
81
82 #[cfg(feature = "diagnostics")]
83 pub fn emit_as_expr_tokens(self) -> TokenStream {
84 let diag: proc_macro2_diagnostics::Diagnostic = self.into();
85 diag.emit_as_expr_tokens()
86 }
87
88 #[cfg(not(feature = "diagnostics"))]
89 pub fn emit_as_expr_tokens(self) -> TokenStream {
90 self.emit_fallback()
91 }
92
93 pub fn emit(self) -> TokenStream {
95 let mut tokens = TokenStream::new();
96
97 if self.level != Level::None {
98 tokens.extend(self.clone().emit_as_item_tokens());
99 }
100
101 for child in self.children {
102 tokens.extend(child.emit());
103 }
104
105 tokens
106 }
107
108 #[cfg(not(feature = "diagnostics"))]
109 fn emit_fallback(self) -> TokenStream {
110 let span = self.span();
111 let msg = &self.message;
112 let mut tokens = if self.level != Level::None && !msg.is_empty() {
113 let prefix = match self.level {
114 Level::Warning => "warning: ",
115 Level::Note => "note: ",
116 Level::Help => "help: ",
117 _ => "",
118 };
119 let full_msg = format!("{prefix}{msg}");
120 quote::quote_spanned! { span => compile_error!(#full_msg); }
121 } else {
122 TokenStream::new()
123 };
124
125 for child in self.children {
126 tokens.extend(child.emit_fallback());
127 }
128
129 tokens
130 }
131}
132
133impl std::fmt::Display for Diagnostic {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 if !self.message.is_empty() {
136 write!(f, "{}: {}", self.level, self.message)?;
137 }
138
139 for (i, child) in self.children.iter().enumerate() {
140 if !self.message.is_empty() || i > 0 {
141 writeln!(f)?;
142 }
143
144 write!(f, "{child}")?;
145 }
146
147 Ok(())
148 }
149}
150
151impl From<syn::Error> for Diagnostic {
152 fn from(error: syn::Error) -> Self {
153 let children: Vec<_> = error
154 .into_iter()
155 .map(|e| Diagnostic {
156 level: Level::Error,
157 message: e.to_string(),
158 spans: e.span().into_spans(),
159 children: Vec::new(),
160 })
161 .collect();
162
163 Self {
164 children,
165 ..Default::default()
166 }
167 }
168}
169
170#[cfg(feature = "diagnostics")]
171impl From<Diagnostic> for proc_macro2_diagnostics::Diagnostic {
172 fn from(value: Diagnostic) -> Self {
173 let span = value.span();
174 let mut diag = Self::spanned(span, value.level.into(), value.message);
175
176 for child in value.children {
177 diag = diag.spanned_child(child.span(), child.level.into(), child.message);
178 }
179
180 diag
181 }
182}
183
184impl IntoIterator for Diagnostic {
185 type Item = Diagnostic;
186 type IntoIter = std::vec::IntoIter<Diagnostic>;
187
188 fn into_iter(self) -> Self::IntoIter {
189 self.children.into_iter()
190 }
191}
192
193impl<'a> IntoIterator for &'a Diagnostic {
194 type Item = &'a Diagnostic;
195 type IntoIter = std::slice::Iter<'a, Diagnostic>;
196
197 fn into_iter(self) -> Self::IntoIter {
198 self.children.iter()
199 }
200}
201
202#[derive(Debug, Clone, Default)]
209pub struct Builder {
210 level: Level,
211 message: String,
212 spans: Vec<Span>,
213 children: Vec<Diagnostic>,
214}
215
216impl Builder {
217 pub fn level(mut self, level: Level) -> Self {
219 self.level = level;
220 self
221 }
222
223 pub fn message(mut self, msg: impl Into<String>) -> Self {
225 self.message = msg.into();
226 self
227 }
228
229 pub fn span(mut self, spans: impl MultiSpan) -> Self {
231 self.spans = spans.into_spans();
232 self
233 }
234
235 #[allow(clippy::should_implement_trait)]
238 pub fn add(mut self, child: impl Into<Builder>) -> Self {
239 self.children.push(child.into().build());
240 self
241 }
242
243 pub fn build(self) -> Diagnostic {
245 Diagnostic {
246 level: self.level,
247 message: self.message,
248 spans: self.spans,
249 children: self.children,
250 }
251 }
252}
253
254impl From<Diagnostic> for Builder {
255 fn from(d: Diagnostic) -> Self {
256 Self {
257 level: d.level,
258 message: d.message,
259 spans: d.spans,
260 children: d.children,
261 }
262 }
263}
264
265pub struct Walk<'a> {
268 stack: Vec<&'a Diagnostic>,
269}
270
271impl<'a> Iterator for Walk<'a> {
272 type Item = &'a Diagnostic;
273
274 fn next(&mut self) -> Option<Self::Item> {
275 let diag = self.stack.pop()?;
276 self.stack.extend(diag.children.iter().rev());
277 Some(diag)
278 }
279}