| 1 | use crate::{abort_now, check_correctness, sealed::Sealed, SpanRange}; |
| 2 | use proc_macro2::Span; |
| 3 | use proc_macro2::TokenStream; |
| 4 | |
| 5 | use quote::{quote_spanned, ToTokens}; |
| 6 | |
| 7 | /// Represents a diagnostic level |
| 8 | /// |
| 9 | /// # Warnings |
| 10 | /// |
| 11 | /// Warnings are ignored on stable/beta |
| 12 | #[derive (Debug, PartialEq)] |
| 13 | #[non_exhaustive ] |
| 14 | pub enum Level { |
| 15 | Error, |
| 16 | Warning, |
| 17 | } |
| 18 | |
| 19 | /// Represents a single diagnostic message |
| 20 | #[derive (Debug)] |
| 21 | #[must_use = "A diagnostic does nothing unless emitted" ] |
| 22 | pub 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 | |
| 30 | /// A collection of methods that do not exist in `proc_macro::Diagnostic` |
| 31 | /// but still useful to have around. |
| 32 | /// |
| 33 | /// This trait is sealed and cannot be implemented outside of `proc_macro_error`. |
| 34 | pub trait DiagnosticExt: Sealed { |
| 35 | /// Create a new diagnostic message that points to the `span_range`. |
| 36 | /// |
| 37 | /// This function is the same as `Diagnostic::spanned` but produces considerably |
| 38 | /// better error messages for multi-token spans on stable. |
| 39 | fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self; |
| 40 | |
| 41 | /// Add another error message to self such that it will be emitted right after |
| 42 | /// the main message. |
| 43 | /// |
| 44 | /// This function is the same as `Diagnostic::span_error` but produces considerably |
| 45 | /// better error messages for multi-token spans on stable. |
| 46 | #[must_use ] |
| 47 | fn span_range_error(self, span_range: SpanRange, msg: String) -> Self; |
| 48 | |
| 49 | /// Attach a "help" note to your main message, the note will have it's own span on nightly. |
| 50 | /// |
| 51 | /// This function is the same as `Diagnostic::span_help` but produces considerably |
| 52 | /// better error messages for multi-token spans on stable. |
| 53 | /// |
| 54 | /// # Span |
| 55 | /// |
| 56 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
| 57 | #[must_use ] |
| 58 | fn span_range_help(self, span_range: SpanRange, msg: String) -> Self; |
| 59 | |
| 60 | /// Attach a note to your main message, the note will have it's own span on nightly. |
| 61 | /// |
| 62 | /// This function is the same as `Diagnostic::span_note` but produces considerably |
| 63 | /// better error messages for multi-token spans on stable. |
| 64 | /// |
| 65 | /// # Span |
| 66 | /// |
| 67 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
| 68 | #[must_use ] |
| 69 | fn span_range_note(self, span_range: SpanRange, msg: String) -> Self; |
| 70 | } |
| 71 | |
| 72 | impl 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 | |
| 101 | impl Diagnostic { |
| 102 | /// Create a new diagnostic message that points to `Span::call_site()` |
| 103 | pub fn new(level: Level, message: String) -> Self { |
| 104 | Diagnostic::spanned(Span::call_site(), level, message) |
| 105 | } |
| 106 | |
| 107 | /// Create a new diagnostic message that points to the `span` |
| 108 | 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 | /// Add another error message to self such that it will be emitted right after |
| 120 | /// the main message. |
| 121 | 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 | /// Attach a "help" note to your main message, the note will have it's own span on nightly. |
| 132 | /// |
| 133 | /// # Span |
| 134 | /// |
| 135 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
| 136 | 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 | /// Attach a "help" note to your main message. |
| 147 | pub fn help(mut self, msg: String) -> Self { |
| 148 | self.suggestions.push((SuggestionKind::Help, msg, None)); |
| 149 | self |
| 150 | } |
| 151 | |
| 152 | /// Attach a note to your main message, the note will have it's own span on nightly. |
| 153 | /// |
| 154 | /// # Span |
| 155 | /// |
| 156 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span |
| 157 | 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 | /// Attach a note to your main message |
| 168 | pub fn note(mut self, msg: String) -> Self { |
| 169 | self.suggestions.push((SuggestionKind::Note, msg, None)); |
| 170 | self |
| 171 | } |
| 172 | |
| 173 | /// The message of main warning/error (no notes attached) |
| 174 | #[must_use ] |
| 175 | pub fn message(&self) -> &str { |
| 176 | &self.msg |
| 177 | } |
| 178 | |
| 179 | /// Abort the proc-macro's execution and display the diagnostic. |
| 180 | /// |
| 181 | /// # Warnings |
| 182 | /// |
| 183 | /// Warnings are not emitted on stable and beta, but this function will abort anyway. |
| 184 | pub fn abort(self) -> ! { |
| 185 | self.emit(); |
| 186 | abort_now() |
| 187 | } |
| 188 | |
| 189 | /// Display the diagnostic while not aborting macro execution. |
| 190 | /// |
| 191 | /// # Warnings |
| 192 | /// |
| 193 | /// Warnings are ignored on stable/beta |
| 194 | pub fn emit(self) { |
| 195 | check_correctness(); |
| 196 | crate::imp::emit_diagnostic(self); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /// **NOT PUBLIC API! NOTHING TO SEE HERE!!!** |
| 201 | #[doc (hidden)] |
| 202 | impl 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 | |
| 218 | impl 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)] |
| 280 | pub(crate) enum SuggestionKind { |
| 281 | Help, |
| 282 | Note, |
| 283 | } |
| 284 | |
| 285 | impl 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" )] |
| 295 | impl 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 | // Currently `syn` builds `compile_error!` invocations |
| 311 | // exclusively in `ident{"..."}` (braced) form which is not |
| 312 | // followed by `;` (semicolon). |
| 313 | // |
| 314 | // But if it changes to `ident("...");` (parenthesized) |
| 315 | // or `ident["..."];` (bracketed) form, |
| 316 | // we will need to skip the `;` as well. |
| 317 | // Highly unlikely, but better safe than sorry. |
| 318 | |
| 319 | if group.delimiter() == Delimiter::Parenthesis |
| 320 | || group.delimiter() == Delimiter::Bracket |
| 321 | { |
| 322 | ts.next().unwrap(); // ; |
| 323 | } |
| 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 | // "abc" => abc |
| 337 | 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 | } |
| 361 | |