1use crate::{abort_now, check_correctness, sealed::Sealed, SpanRange};
2use proc_macro2::Span;
3use proc_macro2::TokenStream;
4
5use quote::{quote_spanned, ToTokens};
6
7#[derive(Debug, PartialEq)]
13#[non_exhaustive]
14pub enum Level {
15 Error,
16 Warning,
17}
18
19#[derive(Debug)]
21#[must_use = "A diagnostic does nothing unless emitted"]
22pub struct Diagnostic {
23 pub(crate) level: Level,
24 pub(crate) span_range: SpanRange,
25 pub(crate) msg: String,
26 pub(crate) suggestions: Vec<(SuggestionKind, String, Option<SpanRange>)>,
27 pub(crate) children: Vec<(SpanRange, String)>,
28}
29
30pub trait DiagnosticExt: Sealed {
35 fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self;
40
41 #[must_use]
47 fn span_range_error(self, span_range: SpanRange, msg: String) -> Self;
48
49 #[must_use]
58 fn span_range_help(self, span_range: SpanRange, msg: String) -> Self;
59
60 #[must_use]
69 fn span_range_note(self, span_range: SpanRange, msg: String) -> Self;
70}
71
72impl DiagnosticExt for Diagnostic {
73 fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self {
74 Diagnostic {
75 level,
76 span_range,
77 msg: message,
78 suggestions: vec![],
79 children: vec![],
80 }
81 }
82
83 fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self {
84 self.children.push((span_range, msg));
85 self
86 }
87
88 fn span_range_help(mut self, span_range: SpanRange, msg: String) -> Self {
89 self.suggestions
90 .push((SuggestionKind::Help, msg, Some(span_range)));
91 self
92 }
93
94 fn span_range_note(mut self, span_range: SpanRange, msg: String) -> Self {
95 self.suggestions
96 .push((SuggestionKind::Note, msg, Some(span_range)));
97 self
98 }
99}
100
101impl Diagnostic {
102 pub fn new(level: Level, message: String) -> Self {
104 Diagnostic::spanned(Span::call_site(), level, message)
105 }
106
107 pub fn spanned(span: Span, level: Level, message: String) -> Self {
109 Diagnostic::spanned_range(
110 SpanRange {
111 first: span,
112 last: span,
113 },
114 level,
115 message,
116 )
117 }
118
119 pub fn span_error(self, span: Span, msg: String) -> Self {
122 self.span_range_error(
123 SpanRange {
124 first: span,
125 last: span,
126 },
127 msg,
128 )
129 }
130
131 pub fn span_help(self, span: Span, msg: String) -> Self {
137 self.span_range_help(
138 SpanRange {
139 first: span,
140 last: span,
141 },
142 msg,
143 )
144 }
145
146 pub fn help(mut self, msg: String) -> Self {
148 self.suggestions.push((SuggestionKind::Help, msg, None));
149 self
150 }
151
152 pub fn span_note(self, span: Span, msg: String) -> Self {
158 self.span_range_note(
159 SpanRange {
160 first: span,
161 last: span,
162 },
163 msg,
164 )
165 }
166
167 pub fn note(mut self, msg: String) -> Self {
169 self.suggestions.push((SuggestionKind::Note, msg, None));
170 self
171 }
172
173 #[must_use]
175 pub fn message(&self) -> &str {
176 &self.msg
177 }
178
179 pub fn abort(self) -> ! {
185 self.emit();
186 abort_now()
187 }
188
189 pub fn emit(self) {
195 check_correctness();
196 crate::imp::emit_diagnostic(self);
197 }
198}
199
200#[doc(hidden)]
202impl Diagnostic {
203 pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self {
204 match suggestion {
205 "help" | "hint" => self.span_help(span, msg),
206 _ => self.span_note(span, msg),
207 }
208 }
209
210 pub fn suggestion(self, suggestion: &str, msg: String) -> Self {
211 match suggestion {
212 "help" | "hint" => self.help(msg),
213 _ => self.note(msg),
214 }
215 }
216}
217
218impl ToTokens for Diagnostic {
219 fn to_tokens(&self, ts: &mut TokenStream) {
220 use std::borrow::Cow;
221
222 fn ensure_lf(buf: &mut String, s: &str) {
223 if s.ends_with('\n') {
224 buf.push_str(s);
225 } else {
226 buf.push_str(s);
227 buf.push('\n');
228 }
229 }
230
231 fn diag_to_tokens(
232 span_range: SpanRange,
233 level: &Level,
234 msg: &str,
235 suggestions: &[(SuggestionKind, String, Option<SpanRange>)],
236 ) -> TokenStream {
237 if *level == Level::Warning {
238 return TokenStream::new();
239 }
240
241 let message = if suggestions.is_empty() {
242 Cow::Borrowed(msg)
243 } else {
244 let mut message = String::new();
245 ensure_lf(&mut message, msg);
246 message.push('\n');
247
248 for (kind, note, _span) in suggestions {
249 message.push_str(" = ");
250 message.push_str(kind.name());
251 message.push_str(": ");
252 ensure_lf(&mut message, note);
253 }
254 message.push('\n');
255
256 Cow::Owned(message)
257 };
258
259 let mut msg = proc_macro2::Literal::string(&message);
260 msg.set_span(span_range.last);
261 let group = quote_spanned!(span_range.last=> { #msg } );
262 quote_spanned!(span_range.first=> compile_error!#group)
263 }
264
265 ts.extend(diag_to_tokens(
266 self.span_range,
267 &self.level,
268 &self.msg,
269 &self.suggestions,
270 ));
271 ts.extend(
272 self.children
273 .iter()
274 .map(|(span_range, msg)| diag_to_tokens(*span_range, &Level::Error, msg, &[])),
275 );
276 }
277}
278
279#[derive(Debug)]
280pub(crate) enum SuggestionKind {
281 Help,
282 Note,
283}
284
285impl SuggestionKind {
286 fn name(&self) -> &'static str {
287 match self {
288 SuggestionKind::Note => "note",
289 SuggestionKind::Help => "help",
290 }
291 }
292}
293
294#[cfg(feature = "syn-error")]
295impl From<syn::Error> for Diagnostic {
296 fn from(err: syn::Error) -> Self {
297 use proc_macro2::{Delimiter, TokenTree};
298
299 fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(SpanRange, String)> {
300 let start_span = ts.next()?.span();
301 ts.next().expect(":1");
302 ts.next().expect("core");
303 ts.next().expect(":2");
304 ts.next().expect(":3");
305 ts.next().expect("compile_error");
306 ts.next().expect("!");
307
308 let lit = match ts.next().unwrap() {
309 TokenTree::Group(group) => {
310 if group.delimiter() == Delimiter::Parenthesis
320 || group.delimiter() == Delimiter::Bracket
321 {
322 ts.next().unwrap(); }
324
325 match group.stream().into_iter().next().unwrap() {
326 TokenTree::Literal(lit) => lit,
327 _ => unreachable!(""),
328 }
329 }
330 _ => unreachable!(""),
331 };
332
333 let last = lit.span();
334 let mut msg = lit.to_string();
335
336 msg.pop();
338 msg.remove(0);
339
340 Some((
341 SpanRange {
342 first: start_span,
343 last,
344 },
345 msg,
346 ))
347 }
348
349 let mut ts = err.to_compile_error().into_iter();
350
351 let (span_range, msg) = gut_error(&mut ts).unwrap();
352 let mut res = Diagnostic::spanned_range(span_range, Level::Error, msg);
353
354 while let Some((span_range, msg)) = gut_error(&mut ts) {
355 res = res.span_range_error(span_range, msg);
356 }
357
358 res
359 }
360}