1use lazy_regex::{lazy_regex, Lazy, Regex};
10use proc_macro::TokenStream;
11use proc_macro2::{Span, TokenStream as TokenStream2};
12use quote::{format_ident, quote, ToTokens};
13use syn::{
14 braced,
15 parse::{self, Parse},
16 parse_macro_input,
17 punctuated::{self, Punctuated},
18 token::{self, Brace},
19 Attribute, DeriveInput, Error, Fields, Generics, Ident, LitStr, Result, Token, Variant,
20 Visibility,
21};
22
23#[cfg(test)]
24mod tests;
25
26type Tuple4<T> = (T, T, T, T);
31
32fn split_fields_attrs(fields: &mut Fields) -> Result<Option<Ident>> {
33 let mut span_ident = None;
34 for (idx, field) in fields.iter_mut().enumerate() {
35 for attr in &field.attrs {
36 if attr.meta.path().is_ident("diag") {
37 attr.parse_nested_meta(|meta| {
38 if meta.path.is_ident("span") {
39 span_ident = Some(field.ident.clone().unwrap_or(format_ident!("_{idx}")))
40 }
41 Ok(())
42 })?
43 }
44 }
45 field.attrs.retain(|attr| !attr.path().is_ident("diag"));
46 }
47 Ok(span_ident)
48}
49
50enum ErrorTree {
52 Prefix {
54 span: Span,
55 attrs: Vec<Attribute>,
56 nodes: Punctuated<ErrorTree, Token![,]>,
57 },
58 Variant {
62 span: Span,
63 attrs: Vec<Attribute>,
64 span_ident: Option<Ident>,
65 ident: Ident,
66 fields: Fields,
67 },
68}
69
70impl ErrorTree {
71 fn attrs(&self) -> &[Attribute] {
72 match self {
73 ErrorTree::Prefix { attrs, .. } => attrs,
74 ErrorTree::Variant { attrs, .. } => attrs,
75 }
76 }
77 fn ident(&self) -> Option<&Ident> {
78 match self {
79 ErrorTree::Prefix { .. } => None,
80 ErrorTree::Variant { ident, .. } => Some(ident),
81 }
82 }
83 fn fields(&self) -> Option<&Fields> {
84 match self {
85 ErrorTree::Prefix { .. } => None,
86 ErrorTree::Variant { fields, .. } => Some(fields),
87 }
88 }
89 fn span_ident(&self) -> Option<Ident> {
90 match self {
91 ErrorTree::Prefix { .. } => None,
92 ErrorTree::Variant { span_ident, .. } => span_ident.clone(),
93 }
94 }
95 fn span(&self) -> Span {
96 match self {
97 ErrorTree::Prefix { span, .. } => *span,
98 ErrorTree::Variant { span, .. } => *span,
99 }
100 }
101}
102
103impl Parse for ErrorTree {
104 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
106 let attrs = input.call(Attribute::parse_outer)?;
107
108 if input.peek(Ident) {
109 let ident: Ident = input.parse()?;
110 let mut fields = if input.peek(token::Brace) {
111 Fields::Named(input.parse()?)
112 } else if input.peek(token::Paren) {
113 Fields::Unnamed(input.parse()?)
114 } else {
115 Fields::Unit
116 };
117 let span_ident = split_fields_attrs(&mut fields)?;
118 Ok(ErrorTree::Variant {
119 span: ident.span(),
120 attrs,
121 span_ident,
122 ident,
123 fields,
124 })
125 } else {
126 let span = input.span();
127 let children;
128 braced!(children in input);
129 let nodes = Punctuated::parse_terminated(&children)?;
130 Ok(ErrorTree::Prefix { span, attrs, nodes })
131 }
132 }
133}
134
135#[derive(Clone, Copy, Default)]
136enum Kind {
137 #[default]
138 Error,
139 Warn,
140}
141
142impl Kind {
143 fn short_str(&self) -> &'static str {
144 match self {
145 Kind::Error => "E",
146 Kind::Warn => "W",
147 }
148 }
149}
150
151impl TryFrom<LitStr> for Kind {
152 type Error = Error;
153
154 fn try_from(value: LitStr) -> Result<Self> {
155 match value.value().as_str() {
156 "error" | "Error" => Ok(Kind::Error),
157 "warn" | "Warn" => Ok(Kind::Warn),
158 _ => Err(Error::new_spanned(
159 value,
160 "Kind must be either `Error` or `Warn`.",
161 )),
162 }
163 }
164}
165
166impl ToTokens for Kind {
167 fn to_tokens(&self, tokens: &mut TokenStream2) {
168 let kind = match self {
169 Kind::Error => quote! { ::error_enum::Kind::Error },
170 Kind::Warn => quote! { ::error_enum::Kind::Warn },
171 };
172 tokens.extend(kind);
173 }
174}
175
176#[derive(Clone)]
178struct Config {
179 kind: Option<Kind>,
180 number: String,
181 msg: Option<LitStr>,
182 attrs: Vec<Attribute>,
183 ident: Option<Ident>,
184 fields: Option<Fields>,
185 span_field: Option<Ident>,
186 label: Option<LitStr>,
187 depth: usize,
188 #[expect(unused)]
189 nested: bool,
190 #[expect(unused)]
191 span: Span,
192}
193
194impl Config {
195 const fn new(span: Span) -> Self {
196 Self {
197 kind: None,
198 number: String::new(),
199 msg: None,
200 attrs: Vec::new(),
201 ident: None,
202 fields: None,
203 span_field: None,
204 label: None,
205 depth: 0,
206 nested: false,
207 span,
208 }
209 }
210 fn process(
211 &self,
212 attrs: &[Attribute],
213 ident: Option<&Ident>,
214 fields: Option<&Fields>,
215 span_field: Option<Ident>,
216 span: Span,
217 ) -> Result<Self> {
218 let mut kind = self.kind;
219 let mut number = self.number.clone();
220 let mut msg = None;
221 let mut label = self.label.clone();
222 let depth = self.depth + 1;
223 let mut nested = false;
224 let mut unused_attrs = Vec::new();
225
226 for attr in attrs {
227 if attr.path().is_ident("diag") {
228 attr.parse_nested_meta(|meta| {
229 if meta.path.is_ident("kind") {
230 let value: LitStr = meta.value()?.parse()?;
231 kind = Some(value.try_into()?);
232 } else if meta.path.is_ident("number") {
233 let value: LitStr = meta.value()?.parse()?;
234 number.push_str(value.value().as_str());
235 } else if meta.path.is_ident("msg") {
236 let value: LitStr = meta.value()?.parse()?;
237 msg = Some(value);
238 } else if meta.path.is_ident("label") {
239 let value: LitStr = meta.value()?.parse()?;
240 label = Some(value);
241 } else if meta.path.is_ident("nested") {
242 nested = true;
243 } else {
244 return Err(Error::new_spanned(meta.path, "Unknown attribute key."));
245 }
246 Ok(())
247 })?
248 } else {
249 unused_attrs.push(attr.clone());
250 }
251 }
252
253 let ident = ident.cloned();
254 let fields = fields.cloned();
255 Ok(Self {
256 kind,
257 number,
258 msg,
259 attrs: unused_attrs,
260 ident,
261 fields,
262 span_field,
263 label,
264 depth,
265 nested,
266 span,
267 })
268 }
269}
270
271struct ErrorTreeIter<'i> {
272 stack: Vec<(punctuated::Iter<'i, ErrorTree>, Config)>,
273}
274
275impl<'i> ErrorTreeIter<'i> {
276 fn new(tree: punctuated::Iter<'i, ErrorTree>, config: Config) -> Result<Self> {
277 Ok(Self {
278 stack: vec![(tree, config)],
279 })
280 }
281 fn process_next(node: &'i ErrorTree, config: &Config, span: Span) -> Result<Config> {
282 let new_config = config.process(
283 node.attrs(),
284 node.ident(),
285 node.fields(),
286 node.span_ident(),
287 span,
288 )?;
289 Ok(new_config)
290 }
291}
292
293impl<'i> Iterator for ErrorTreeIter<'i> {
294 type Item = Result<Config>;
295
296 fn next(&mut self) -> Option<Self::Item> {
297 while let Some((slice, config)) = self.stack.last_mut() {
298 if let Some(node) = slice.next() {
299 let config = Self::process_next(node, config, node.span())
300 .map(Some)
301 .transpose()?;
302 if let Ok(config) = &config {
303 if let ErrorTree::Prefix { nodes, .. } = node {
304 self.stack.push((nodes.iter(), config.clone()));
305 }
306 }
307 return Some(config);
308 } else {
309 self.stack.pop();
310 }
311 }
312 None
313 }
314}
315
316struct ErrorEnum {
324 attrs: Vec<Attribute>,
325 vis: Visibility,
326 name: Ident,
327 generics: Generics,
328 brace: Brace,
329 roots: Punctuated<ErrorTree, Token![,]>,
330 config: Config,
331}
332
333impl ErrorEnum {
334 fn iter(&self) -> Result<ErrorTreeIter<'_>> {
335 ErrorTreeIter::new(self.roots.iter(), self.config.clone())
336 }
337}
338
339impl Parse for ErrorEnum {
340 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
341 let mut attrs = input.call(Attribute::parse_outer)?;
342 let vis = input.parse()?;
343 let name: Ident = input.parse()?;
344 let generics = input.parse()?;
345 let children;
346 let brace = braced!(children in input);
347 let config = Config::new(name.span()).process(&attrs, None, None, None, name.span())?;
348 attrs.retain(|attr| !attr.path().is_ident("diag"));
349
350 let roots = Punctuated::parse_terminated(&children)?;
351 Ok(Self {
352 attrs,
353 vis,
354 generics,
355 name,
356 brace,
357 roots,
358 config,
359 })
360 }
361}
362
363impl TryFrom<DeriveInput> for ErrorEnum {
364 type Error = Error;
365
366 fn try_from(value: DeriveInput) -> Result<Self> {
367 let DeriveInput {
368 mut attrs,
369 vis,
370 ident,
371 generics,
372 data,
373 } = value;
374 match data {
375 syn::Data::Enum(data_enum) => {
376 let mut roots = Punctuated::new();
377 for pair in data_enum.variants.into_pairs() {
378 let (mut variant, comma) = pair.into_tuple();
379 let span = variant.ident.span();
380 let span_ident = split_fields_attrs(&mut variant.fields)?;
381 let node = ErrorTree::Variant {
382 span,
383 attrs: variant.attrs,
384 ident: variant.ident,
385 fields: variant.fields,
386 span_ident,
387 };
388 roots.push_value(node);
389 if let Some(comma) = comma {
390 roots.push_punct(comma);
391 }
392 }
393 let config =
394 Config::new(ident.span()).process(&attrs, None, None, None, ident.span())?;
395 attrs.retain(|attr| !attr.path().is_ident("diag"));
396 Ok(Self {
397 attrs,
398 vis,
399 name: ident,
400 generics,
401 brace: data_enum.brace_token,
402 roots,
403 config,
404 })
405 }
406 _ => Err(Error::new_spanned(
407 ident,
408 "ErrorEnum can only be derived for enums.",
409 )),
410 }
411 }
412}
413
414impl ErrorEnum {
415 fn doc(&self) -> Result<Vec<String>> {
416 self.iter()?
417 .map(|config| {
418 let Config {
419 number,
420 depth,
421 ident,
422 msg,
423 kind,
424 ..
425 } = config?;
426 let indent = " ".repeat(depth - 2);
427 let msg = msg.as_ref().map(|s| s.value());
428 let kind = kind.unwrap_or_default().short_str();
429 Ok(match (ident, msg) {
430 (Some(ident), Some(msg)) => {
431 format!("{indent}- `{kind}{number}`(**{ident}**): {msg}")
432 }
433 (None, Some(msg)) => format!("{indent}- `{kind}{number}`: {msg}"),
434 (Some(ident), None) => format!("{indent}- `{kind}{number}`(**{ident}**)"),
435 (None, None) => format!("{indent}- `{kind}{number}`"),
436 })
437 })
438 .collect()
439 }
440 fn variants(&self) -> Result<Vec<Variant>> {
441 self.iter()?
442 .filter_map(|config| {
443 config
444 .map(
445 |Config {
446 kind,
447 msg,
448 number,
449 attrs,
450 ident,
451 fields,
452 ..
453 }| {
454 Some((kind, msg, number, attrs, ident?, fields?))
455 },
456 )
457 .transpose()
458 })
459 .map(|config| {
460 let (kind, msg, number, mut attrs, ident, fields) = config?;
461
462 let kind = kind.unwrap_or_default();
463 let code = format!("{}{}", kind.short_str(), number);
464
465 let doc = match msg {
466 Some(msg) => {
467 format!("`{code}`: {msg}", msg = msg.value())
468 }
469 None => format!("`{code}`"),
470 };
471
472 attrs.push(syn::parse_quote! {
473 #[doc = #doc]
474 });
475 attrs.push(syn::parse_quote! {
476 #[doc(alias = #code)]
477 });
478
479 Ok(Variant {
480 attrs,
481 ident,
482 fields,
483 discriminant: None,
484 })
485 })
486 .collect()
487 }
488 fn used_unnamed_fields(msg: &LitStr) -> Result<Vec<Ident>> {
489 static ARG: Lazy<Regex> = lazy_regex!(r#"(^|[^\{])(\{\{)*\{(?<index>\d+)(:[^\{\}]*)?\}"#);
490 ARG.captures_iter(msg.value().as_str())
491 .map(|cap| {
492 let index = cap
493 .name("index")
494 .ok_or_else(|| Error::new_spanned(msg, "Invalid argument index."))?
495 .as_str()
496 .parse::<usize>()
497 .map_err(|err| {
498 Error::new_spanned(msg, format!("Invalid argument index: {err}"))
499 })?;
500 Ok(format_ident!("_{}", index))
501 })
502 .collect()
503 }
504 fn display_branch(ident: &Ident, fields: &Fields, msg: &LitStr) -> Result<TokenStream2> {
505 match fields {
506 Fields::Named(named) => {
507 let members = named.named.iter().map(|f| f.ident.as_ref());
508 Ok(quote! {
509 #[allow(unused_variables)]
510 Self::#ident { #(#members),* } => ::core::write!(f, #msg),
511 })
512 }
513 Fields::Unnamed(unnamed) => {
514 let params = (0..unnamed.unnamed.len()).map(|i| format_ident!("_{}", i));
515 let args = Self::used_unnamed_fields(msg)?;
516 Ok(quote! {
517 Self::#ident ( #(#params),* ) => ::core::write!(f, #msg #(, #args)* ),
518 })
519 }
520 Fields::Unit => Ok(quote! {
521 Self::#ident => ::core::write!(f, #msg),
522 }),
523 }
524 }
525 fn display(&self) -> Result<Vec<TokenStream2>> {
526 self.iter()?
527 .filter_map(|config| {
528 config
529 .map(
530 |Config {
531 msg, ident, fields, ..
532 }| { Some((msg, ident?, fields?)) },
533 )
534 .transpose()
535 })
536 .map(|config| {
537 let (msg, ident, fields) = config?;
538 let msg = msg.ok_or_else(|| {
539 Error::new_spanned(
540 &ident,
541 "Missing message. Consider using `#[diag(msg = \"...\")]`",
542 )
543 })?;
544 Self::display_branch(&ident, &fields, &msg)
545 })
546 .collect()
547 }
548 fn primary_label_branch(
549 ident: &Ident,
550 fields: &Fields,
551 label: &LitStr,
552 ) -> Result<TokenStream2> {
553 match fields {
554 Fields::Named(named) => {
555 let members = named.named.iter().map(|f| f.ident.as_ref());
556 Ok(quote! {
557 #[allow(unused_variables)]
558 Self::#ident { #(#members),* } => ::std::format!(#label),
559 })
560 }
561 Fields::Unnamed(unnamed) => {
562 let params = (0..unnamed.unnamed.len()).map(|i| format_ident!("_{}", i));
563 let args = Self::used_unnamed_fields(label)?;
564 Ok(quote! {
565 Self::#ident ( #(#params),* ) => ::std::format!(#label #(, #args)* ),
566 })
567 }
568 Fields::Unit => Ok(quote! {
569 Self::#ident => ::std::format!(#label),
570 }),
571 }
572 }
573 fn primary_label(&self) -> Result<Vec<TokenStream2>> {
574 self.iter()?
575 .filter_map(|config| {
576 config
577 .map(
578 |Config {
579 msg,
580 ident,
581 fields,
582 label,
583 ..
584 }| { Some((msg, ident?, fields?, label)) },
585 )
586 .transpose()
587 })
588 .map(|config| {
589 let (msg, ident, fields, label) = config?;
590 let label = label.or(msg).ok_or_else(|| {
591 Error::new_spanned(
592 &ident,
593 "Missing label or message. Consider using `#[diag(label = \"...\")]`",
594 )
595 })?;
596 Self::primary_label_branch(&ident, &fields, &label)
597 })
598 .collect()
599 }
600 fn impl_error_enum_branch(
601 ident: &Ident,
602 fields: &Fields,
603 span_field: Option<Ident>,
604 kind: &Kind,
605 number: &str,
606 ) -> Result<Tuple4<TokenStream2>> {
607 let branch_ignored = match fields {
608 Fields::Named(_) => quote! { { .. } },
609 Fields::Unnamed(_) => quote! { (..) },
610 Fields::Unit => quote! {},
611 };
612 let code = format!("{}{}", kind.short_str(), number);
613
614 let kind = quote! {
615 Self::#ident #branch_ignored => #kind,
616 };
617 let number = quote! {
618 Self::#ident #branch_ignored => #number,
619 };
620 let code = quote! {
621 Self::#ident #branch_ignored => #code,
622 };
623 let span = if let Some(span_field) = span_field {
624 quote! {<::error_enum::SimpleSpan as ::core::convert::From<_>>::from(#span_field)}
625 } else {
626 quote! {<::error_enum::SimpleSpan as ::core::default::Default>::default()}
627 };
628 let primary_span = match fields {
629 Fields::Named(named) => {
630 let members = named.named.iter().map(|f| f.ident.as_ref());
631 quote! {
632 #[allow(unused_variables)]
633 Self::#ident { #(#members),* } => #span,
634 }
635 }
636 Fields::Unnamed(unnamed) => {
637 let params = (0..unnamed.unnamed.len()).map(|i| format_ident!("_{}", i));
638 quote! {
639 #[allow(unused_variables)]
640 Self::#ident ( #(#params),* ) => #span,
641 }
642 }
643 Fields::Unit => quote! {
644 Self::#ident => #span,
645 },
646 };
647 Ok((kind, number, code, primary_span))
648 }
649 fn impl_error_enum(&self) -> Result<Tuple4<Vec<TokenStream2>>> {
650 self.iter()?
651 .filter_map(|config| {
652 config
653 .map(
654 |Config {
655 ident,
656 fields,
657 span_field,
658 kind,
659 number,
660 ..
661 }| {
662 Some((ident?, fields?, span_field, kind?, number))
663 },
664 )
665 .transpose()
666 })
667 .map(|config| {
668 let (ident, fields, span_field, kind, number) = config?;
669 Self::impl_error_enum_branch(&ident, &fields, span_field, &kind, &number)
670 })
671 .collect()
672 }
673 fn try_to_tokens(&self, tokens: &mut TokenStream2) -> Result<()> {
674 let attrs = &self.attrs;
675 let vis = &self.vis;
676 let name = &self.name;
677 let generics = &self.generics;
678
679 let doc = self.doc()?;
680
681 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
682
683 let variants = self.variants()?;
684
685 tokens.extend(quote! {
686 #(#attrs)*
687 #[doc = "List of error variants:"]
688 #(
689 #[doc = #doc]
690 )*
691 #vis enum #name #generics
692 });
693 self.brace.surround(tokens, |tokens| {
694 tokens.extend(quote! { #(#variants, )* });
695 });
696
697 let display = self.display()?;
698 tokens.extend(quote! {
699 impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
700 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
701 match self {
702 #(#display)*
703 }
704 }
705 }
706 impl #impl_generics ::core::error::Error for #name #ty_generics #where_clause {}
707 });
708
709 let (kind, number, code, primary_span) = self.impl_error_enum()?;
710 let primary_label = self.primary_label()?;
711 tokens.extend(quote! {
712 impl #impl_generics ::error_enum::ErrorEnum for #name #ty_generics #where_clause {
713 type Span = ::error_enum::SimpleSpan;
714 type Message = ::std::string::String;
715
716 fn kind(&self) -> ::error_enum::Kind {
717 match self {
718 #(#kind)*
719 }
720 }
721 fn number(&self) -> &::core::primitive::str {
722 match self {
723 #(#number)*
724 }
725 }
726 fn code(&self) -> &::core::primitive::str {
727 match self {
728 #(#code)*
729 }
730 }
731 fn primary_span(&self) -> ::error_enum::SimpleSpan {
732 match self {
733 #(#primary_span)*
734 }
735 }
736 fn primary_message(&self) -> ::std::string::String {
737 ::std::format!("{self}")
738 }
739 fn primary_label(&self) -> ::std::string::String {
740 match self {
741 #(#primary_label)*
742 }
743 }
744 }
745 });
746
747 Ok(())
748 }
749}
750
751impl ToTokens for ErrorEnum {
752 fn to_tokens(&self, tokens: &mut TokenStream2) {
753 let mut buffer = TokenStream2::new();
754 self.try_to_tokens(&mut buffer)
755 .inspect(|()| tokens.extend(buffer))
756 .unwrap_or_else(|err| {
757 let diag = err.to_compile_error();
758 tokens.extend(diag);
759 });
760 }
761}
762
763#[proc_macro]
806pub fn error_type(token: TokenStream) -> TokenStream {
807 let error = parse_macro_input!(token as ErrorEnum);
808 error.to_token_stream().into()
809}
810
811#[proc_macro_derive(ErrorType, attributes(diag))]
815pub fn error_enum(token: TokenStream) -> TokenStream {
816 let input: DeriveInput = parse_macro_input!(token as DeriveInput);
817 let error = ErrorEnum::try_from(input)
818 .map_or_else(|err| err.to_compile_error(), |e| e.to_token_stream());
819 error.into()
820}