1 use crate::{abort_now, check_correctness}; 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) start: Span, 25 pub(crate) end: Span, 26 pub(crate) msg: String, 27 pub(crate) suggestions: Vec<(SuggestionKind, String, Option<Span>)>, 28 pub(crate) children: Vec<(Span, Span, String)>, 29 } 30 31 impl Diagnostic { 32 /// Create a new diagnostic message that points to `Span::call_site()` new(level: Level, message: String) -> Self33 pub fn new(level: Level, message: String) -> Self { 34 Diagnostic::spanned(Span::call_site(), level, message) 35 } 36 37 /// Create a new diagnostic message that points to the `span` spanned(span: Span, level: Level, message: String) -> Self38 pub fn spanned(span: Span, level: Level, message: String) -> Self { 39 Diagnostic::double_spanned(span, span, level, message) 40 } 41 42 /// Add another error message to self such that it will be emitted right after 43 /// the main message. span_error(self, span: Span, msg: String) -> Self44 pub fn span_error(self, span: Span, msg: String) -> Self { 45 self.double_span_error(span, span, msg) 46 } 47 48 /// Attach a "help" note to your main message, the note will have it's own span on nightly. 49 /// 50 /// # Span 51 /// 52 /// The span is ignored on stable, the note effectively inherits its parent's (main message) span span_help(mut self, span: Span, msg: String) -> Self53 pub fn span_help(mut self, span: Span, msg: String) -> Self { 54 self.suggestions 55 .push((SuggestionKind::Help, msg, Some(span))); 56 self 57 } 58 59 /// Attach a "help" note to your main message. help(mut self, msg: String) -> Self60 pub fn help(mut self, msg: String) -> Self { 61 self.suggestions.push((SuggestionKind::Help, msg, None)); 62 self 63 } 64 65 /// Attach a note to your main message, the note will have it's own span on nightly. 66 /// 67 /// # Span 68 /// 69 /// The span is ignored on stable, the note effectively inherits its parent's (main message) span span_note(mut self, span: Span, msg: String) -> Self70 pub fn span_note(mut self, span: Span, msg: String) -> Self { 71 self.suggestions 72 .push((SuggestionKind::Note, msg, Some(span))); 73 self 74 } 75 76 /// Attach a note to your main message note(mut self, msg: String) -> Self77 pub fn note(mut self, msg: String) -> Self { 78 self.suggestions.push((SuggestionKind::Note, msg, None)); 79 self 80 } 81 82 /// The message of main warning/error (no notes attached) message(&self) -> &str83 pub fn message(&self) -> &str { 84 &self.msg 85 } 86 87 /// Abort the proc-macro's execution and display the diagnostic. 88 /// 89 /// # Warnings 90 /// 91 /// Warnings do not get emitted on stable/beta but this function will abort anyway. abort(self) -> !92 pub fn abort(self) -> ! { 93 self.emit(); 94 abort_now() 95 } 96 97 /// Display the diagnostic while not aborting macro execution. 98 /// 99 /// # Warnings 100 /// 101 /// Warnings are ignored on stable/beta emit(self)102 pub fn emit(self) { 103 check_correctness(); 104 crate::imp::emit_diagnostic(self); 105 } 106 } 107 108 /// **NOT PUBLIC API! NOTHING TO SEE HERE!!!** 109 #[doc(hidden)] 110 impl Diagnostic { double_spanned(start: Span, end: Span, level: Level, message: String) -> Self111 pub fn double_spanned(start: Span, end: Span, level: Level, message: String) -> Self { 112 Diagnostic { 113 level, 114 start, 115 end, 116 msg: message, 117 suggestions: vec![], 118 children: vec![], 119 } 120 } 121 double_span_error(mut self, start: Span, end: Span, msg: String) -> Self122 pub fn double_span_error(mut self, start: Span, end: Span, msg: String) -> Self { 123 self.children.push((start, end, msg)); 124 self 125 } 126 span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self127 pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self { 128 match suggestion { 129 "help" | "hint" => self.span_help(span, msg), 130 _ => self.span_note(span, msg), 131 } 132 } 133 suggestion(self, suggestion: &str, msg: String) -> Self134 pub fn suggestion(self, suggestion: &str, msg: String) -> Self { 135 match suggestion { 136 "help" | "hint" => self.help(msg), 137 _ => self.note(msg), 138 } 139 } 140 } 141 142 impl ToTokens for Diagnostic { to_tokens(&self, ts: &mut TokenStream)143 fn to_tokens(&self, ts: &mut TokenStream) { 144 use std::borrow::Cow; 145 146 fn ensure_lf(buf: &mut String, s: &str) { 147 if s.ends_with('\n') { 148 buf.push_str(s); 149 } else { 150 buf.push_str(s); 151 buf.push('\n'); 152 } 153 } 154 155 fn diag_to_tokens( 156 start: Span, 157 end: Span, 158 level: &Level, 159 msg: &str, 160 suggestions: &[(SuggestionKind, String, Option<Span>)], 161 ) -> TokenStream { 162 if *level == Level::Warning { 163 return TokenStream::new(); 164 } 165 166 let message = if suggestions.is_empty() { 167 Cow::Borrowed(msg) 168 } else { 169 let mut message = String::new(); 170 ensure_lf(&mut message, msg); 171 message.push('\n'); 172 173 for (kind, note, _span) in suggestions { 174 message.push_str(" = "); 175 message.push_str(kind.name()); 176 message.push_str(": "); 177 ensure_lf(&mut message, note); 178 } 179 message.push('\n'); 180 181 Cow::Owned(message) 182 }; 183 184 let msg = syn::LitStr::new(&*message, end); 185 let group = quote_spanned!(end=> { #msg } ); 186 quote_spanned!(start=> compile_error!#group) 187 } 188 189 ts.extend(diag_to_tokens( 190 self.start, 191 self.end, 192 &self.level, 193 &self.msg, 194 &self.suggestions, 195 )); 196 ts.extend( 197 self.children 198 .iter() 199 .map(|(start, end, msg)| diag_to_tokens(*start, *end, &Level::Error, &msg, &[])), 200 ); 201 } 202 } 203 204 #[derive(Debug)] 205 pub(crate) enum SuggestionKind { 206 Help, 207 Note, 208 } 209 210 impl SuggestionKind { name(&self) -> &'static str211 fn name(&self) -> &'static str { 212 match self { 213 SuggestionKind::Note => "note", 214 SuggestionKind::Help => "help", 215 } 216 } 217 } 218 219 impl From<syn::Error> for Diagnostic { from(err: syn::Error) -> Self220 fn from(err: syn::Error) -> Self { 221 use proc_macro2::{Delimiter, TokenTree}; 222 223 fn gut_error(ts: &mut impl Iterator<Item = TokenTree>) -> Option<(Span, Span, String)> { 224 let start = match ts.next() { 225 // compile_error 226 None => return None, 227 Some(tt) => tt.span(), 228 }; 229 ts.next().unwrap(); // ! 230 231 let lit = match ts.next().unwrap() { 232 TokenTree::Group(group) => { 233 // Currently `syn` builds `compile_error!` invocations 234 // exclusively in `ident{"..."}` (braced) form which is not 235 // followed by `;` (semicolon). 236 // 237 // But if it changes to `ident("...");` (parenthesized) 238 // or `ident["..."];` (bracketed) form, 239 // we will need to skip the `;` as well. 240 // Highly unlikely, but better safe than sorry. 241 242 if group.delimiter() == Delimiter::Parenthesis 243 || group.delimiter() == Delimiter::Bracket 244 { 245 ts.next().unwrap(); // ; 246 } 247 248 match group.stream().into_iter().next().unwrap() { 249 TokenTree::Literal(lit) => lit, 250 _ => unreachable!(), 251 } 252 } 253 _ => unreachable!(), 254 }; 255 256 let end = lit.span(); 257 let mut msg = lit.to_string(); 258 259 // "abc" => abc 260 msg.pop(); 261 msg.remove(0); 262 263 Some((start, end, msg)) 264 } 265 266 let mut ts = err.to_compile_error().into_iter(); 267 268 let (start, end, msg) = gut_error(&mut ts).unwrap(); 269 let mut res = Diagnostic::double_spanned(start, end, Level::Error, msg); 270 271 while let Some((start, end, msg)) = gut_error(&mut ts) { 272 res = res.double_span_error(start, end, msg); 273 } 274 275 res 276 } 277 } 278