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 pub enum Level { 14 Error, 15 Warning, 16 #[doc(hidden)] 17 NonExhaustive, 18 } 19 20 /// Represents a single diagnostic message 21 #[derive(Debug)] 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. spanned_range(span_range: SpanRange, level: Level, message: String) -> Self39 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. span_range_error(self, span_range: SpanRange, msg: String) -> Self46 fn span_range_error(self, span_range: SpanRange, msg: String) -> Self; 47 48 /// Attach a "help" note to your main message, the note will have it's own span on nightly. 49 /// 50 /// This function is the same as `Diagnostic::span_help` but produces considerably 51 /// better error messages for multi-token spans on stable. 52 /// 53 /// # Span 54 /// 55 /// The span is ignored on stable, the note effectively inherits its parent's (main message) span span_range_help(self, span_range: SpanRange, msg: String) -> Self56 fn span_range_help(self, span_range: SpanRange, msg: String) -> Self; 57 58 /// Attach a note to your main message, the note will have it's own span on nightly. 59 /// 60 /// This function is the same as `Diagnostic::span_note` but produces considerably 61 /// better error messages for multi-token spans on stable. 62 /// 63 /// # Span 64 /// 65 /// The span is ignored on stable, the note effectively inherits its parent's (main message) span span_range_note(self, span_range: SpanRange, msg: String) -> Self66 fn span_range_note(self, span_range: SpanRange, msg: String) -> Self; 67 } 68 69 impl DiagnosticExt for Diagnostic { spanned_range(span_range: SpanRange, level: Level, message: String) -> Self70 fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self { 71 Diagnostic { 72 level, 73 span_range, 74 msg: message, 75 suggestions: vec![], 76 children: vec![], 77 } 78 } 79 span_range_error(mut self, span_range: SpanRange, msg: String) -> Self80 fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self { 81 self.children.push((span_range, msg)); 82 self 83 } 84 span_range_help(mut self, span_range: SpanRange, msg: String) -> Self85 fn span_range_help(mut self, span_range: SpanRange, msg: String) -> Self { 86 self.suggestions 87 .push((SuggestionKind::Help, msg, Some(span_range))); 88 self 89 } 90 span_range_note(mut self, span_range: SpanRange, msg: String) -> Self91 fn span_range_note(mut self, span_range: SpanRange, msg: String) -> Self { 92 self.suggestions 93 .push((SuggestionKind::Note, msg, Some(span_range))); 94 self 95 } 96 } 97 98 impl Diagnostic { 99 /// Create a new diagnostic message that points to `Span::call_site()` new(level: Level, message: String) -> Self100 pub fn new(level: Level, message: String) -> Self { 101 Diagnostic::spanned(Span::call_site(), level, message) 102 } 103 104 /// Create a new diagnostic message that points to the `span` spanned(span: Span, level: Level, message: String) -> Self105 pub fn spanned(span: Span, level: Level, message: String) -> Self { 106 Diagnostic::spanned_range( 107 SpanRange { 108 first: span, 109 last: span, 110 }, 111 level, 112 message, 113 ) 114 } 115 116 /// Add another error message to self such that it will be emitted right after 117 /// the main message. span_error(self, span: Span, msg: String) -> Self118 pub fn span_error(self, span: Span, msg: String) -> Self { 119 self.span_range_error( 120 SpanRange { 121 first: span, 122 last: span, 123 }, 124 msg, 125 ) 126 } 127 128 /// Attach a "help" note to your main message, the note will have it's own span on nightly. 129 /// 130 /// # Span 131 /// 132 /// The span is ignored on stable, the note effectively inherits its parent's (main message) span span_help(self, span: Span, msg: String) -> Self133 pub fn span_help(self, span: Span, msg: String) -> Self { 134 self.span_range_help( 135 SpanRange { 136 first: span, 137 last: span, 138 }, 139 msg, 140 ) 141 } 142 143 /// Attach a "help" note to your main message. help(mut self, msg: String) -> Self144 pub fn help(mut self, msg: String) -> Self { 145 self.suggestions.push((SuggestionKind::Help, msg, None)); 146 self 147 } 148 149 /// Attach a note to your main message, the note will have it's own span on nightly. 150 /// 151 /// # Span 152 /// 153 /// The span is ignored on stable, the note effectively inherits its parent's (main message) span span_note(self, span: Span, msg: String) -> Self154 pub fn span_note(self, span: Span, msg: String) -> Self { 155 self.span_range_note( 156 SpanRange { 157 first: span, 158 last: span, 159 }, 160 msg, 161 ) 162 } 163 164 /// Attach a note to your main message note(mut self, msg: String) -> Self165 pub fn note(mut self, msg: String) -> Self { 166 self.suggestions.push((SuggestionKind::Note, msg, None)); 167 self 168 } 169 170 /// The message of main warning/error (no notes attached) message(&self) -> &str171 pub fn message(&self) -> &str { 172 &self.msg 173 } 174 175 /// Abort the proc-macro's execution and display the diagnostic. 176 /// 177 /// # Warnings 178 /// 179 /// Warnings are not emitted on stable and beta, but this function will abort anyway. abort(self) -> !180 pub fn abort(self) -> ! { 181 self.emit(); 182 abort_now() 183 } 184 185 /// Display the diagnostic while not aborting macro execution. 186 /// 187 /// # Warnings 188 /// 189 /// Warnings are ignored on stable/beta emit(self)190 pub fn emit(self) { 191 check_correctness(); 192 crate::imp::emit_diagnostic(self); 193 } 194 } 195 196 /// **NOT PUBLIC API! NOTHING TO SEE HERE!!!** 197 #[doc(hidden)] 198 impl Diagnostic { span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self199 pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self { 200 match suggestion { 201 "help" | "hint" => self.span_help(span, msg), 202 _ => self.span_note(span, msg), 203 } 204 } 205 suggestion(self, suggestion: &str, msg: String) -> Self206 pub fn suggestion(self, suggestion: &str, msg: String) -> Self { 207 match suggestion { 208 "help" | "hint" => self.help(msg), 209 _ => self.note(msg), 210 } 211 } 212 } 213 214 impl ToTokens for Diagnostic { to_tokens(&self, ts: &mut TokenStream)215 fn to_tokens(&self, ts: &mut TokenStream) { 216 use std::borrow::Cow; 217 218 fn ensure_lf(buf: &mut String, s: &str) { 219 if s.ends_with('\n') { 220 buf.push_str(s); 221 } else { 222 buf.push_str(s); 223 buf.push('\n'); 224 } 225 } 226 227 fn diag_to_tokens( 228 span_range: SpanRange, 229 level: &Level, 230 msg: &str, 231 suggestions: &[(SuggestionKind, String, Option<SpanRange>)], 232 ) -> TokenStream { 233 if *level == Level::Warning { 234 return TokenStream::new(); 235 } 236 237 let message = if suggestions.is_empty() { 238 Cow::Borrowed(msg) 239 } else { 240 let mut message = String::new(); 241 ensure_lf(&mut message, msg); 242 message.push('\n'); 243 244 for (kind, note, _span) in suggestions { 245 message.push_str(" = "); 246 message.push_str(kind.name()); 247 message.push_str(": "); 248 ensure_lf(&mut message, note); 249 } 250 message.push('\n'); 251 252 Cow::Owned(message) 253 }; 254 255 let mut msg = proc_macro2::Literal::string(&message); 256 msg.set_span(span_range.last); 257 let group = quote_spanned!(span_range.last=> { #msg } ); 258 quote_spanned!(span_range.first=> compile_error!#group) 259 } 260 261 ts.extend(diag_to_tokens( 262 self.span_range, 263 &self.level, 264 &self.msg, 265 &self.suggestions, 266 )); 267 ts.extend( 268 self.children 269 .iter() 270 .map(|(span_range, msg)| diag_to_tokens(*span_range, &Level::Error, &msg, &[])), 271 ); 272 } 273 } 274 275 #[derive(Debug)] 276 pub(crate) enum SuggestionKind { 277 Help, 278 Note, 279 } 280 281 impl SuggestionKind { name(&self) -> &'static str282 fn name(&self) -> &'static str { 283 match self { 284 SuggestionKind::Note => "note", 285 SuggestionKind::Help => "help", 286 } 287 } 288 } 289 290 #[cfg(feature = "syn-error")] 291 impl From<syn::Error> for Diagnostic { from(err: syn::Error) -> Self292 fn from(err: syn::Error) -> Self { 293 use proc_macro2::{Delimiter, TokenTree}; 294 295 fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(SpanRange, String)> { 296 let first = match ts.next() { 297 // compile_error 298 None => return None, 299 Some(tt) => tt.span(), 300 }; 301 ts.next().unwrap(); // ! 302 303 let lit = match ts.next().unwrap() { 304 TokenTree::Group(group) => { 305 // Currently `syn` builds `compile_error!` invocations 306 // exclusively in `ident{"..."}` (braced) form which is not 307 // followed by `;` (semicolon). 308 // 309 // But if it changes to `ident("...");` (parenthesized) 310 // or `ident["..."];` (bracketed) form, 311 // we will need to skip the `;` as well. 312 // Highly unlikely, but better safe than sorry. 313 314 if group.delimiter() == Delimiter::Parenthesis 315 || group.delimiter() == Delimiter::Bracket 316 { 317 ts.next().unwrap(); // ; 318 } 319 320 match group.stream().into_iter().next().unwrap() { 321 TokenTree::Literal(lit) => lit, 322 _ => unreachable!(), 323 } 324 } 325 _ => unreachable!(), 326 }; 327 328 let last = lit.span(); 329 let mut msg = lit.to_string(); 330 331 // "abc" => abc 332 msg.pop(); 333 msg.remove(0); 334 335 Some((SpanRange { first, last }, msg)) 336 } 337 338 let mut ts = err.to_compile_error().into_iter(); 339 340 let (span_range, msg) = gut_error(&mut ts).unwrap(); 341 let mut res = Diagnostic::spanned_range(span_range, Level::Error, msg); 342 343 while let Some((span_range, msg)) = gut_error(&mut ts) { 344 res = res.span_range_error(span_range, msg); 345 } 346 347 res 348 } 349 } 350