aboutsummaryrefslogtreecommitdiff
path: root/macros/src/util/error.rs
blob: b9f67c49dcbecd7bf6ebe6eeabea2c1fd7809045 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use proc_macro2::Span;
use proc_macro_error::Diagnostic;

/// Used to create a error enum that converts into a [`Diagnostic`].
///
/// [`Diagnostic`]: proc_macro_error::Diagnostic
macro_rules! diagnostic_error_enum {
    ($(#[$meta: meta])* $visibility: vis enum $name: ident {
        $(
            #[error($($error: tt)*), span = $error_span: expr]
            $(#[note($($note: tt)*)$(, span = $note_span: expr)?])*
            $(#[help($($help: tt)*)$(, span = $help_span: expr)?])*
            $(#[err($($err: tt)*)$(, span = $err_span: expr)?])*
            $(#[source($source: ident)])?
            $variant: ident {
                $($variant_field: ident: $variant_field_type: ty),*
            },
        )*
    }) => {
        $(#[$meta])*
        #[derive(Debug, Clone)]
        $visibility enum $name
        {
            $(
                $variant {
                    $($variant_field: $variant_field_type),*
                },
            )*
        }

        impl From<$name> for ::proc_macro_error::Diagnostic
        {
            #[must_use]
            fn from(err: $name) -> Self
            {
                use $crate::util::error::DiagnosticErrorVariantInfo;

                let DiagnosticErrorVariantInfo {
                    error, span, notes, helps, errs, source
                } = match err {
                    $(
                        $name::$variant { $($variant_field),* } => {
                            DiagnosticErrorVariantInfo {
                                error: format!($($error)*),
                                span: $error_span,
                                notes: vec![$(
                                    (
                                        format!($($note)*),
                                        $crate::util::or!(
                                            ($($note_span)?)
                                            else (::proc_macro2::Span::call_site())
                                        )
                                    )
                                ),*],
                                helps: vec![$(
                                    (
                                        format!($($help)*),
                                        $crate::util::or!(
                                            ($($help_span)?)
                                            else (::proc_macro2::Span::call_site())
                                        )
                                    )
                                ),*],
                                errs: vec![$(
                                    (
                                        format!($($err)*),
                                        $crate::util::or!(
                                            ($($err_span)?)
                                            else (::proc_macro2::Span::call_site())
                                        )
                                    )
                                ),*],
                                source: $crate::util::to_option!($($source.into())?)
                            }
                        }
                    ),*
                };

                if let Some(source_diagnostic) = source {
                    source_diagnostic.emit();
                }

                let mut diagnostic = ::proc_macro_error::Diagnostic::spanned(
                    span,
                    ::proc_macro_error::Level::Error,
                    error
                );

                if !notes.is_empty() {
                    for (note, note_span) in notes {
                        diagnostic = diagnostic.span_note(note_span, note);
                    }
                }

                if !helps.is_empty() {
                    for (help, help_span) in helps {
                        diagnostic = diagnostic.span_help(help_span, help);
                    }
                }

                if !errs.is_empty() {
                    for (err, err_span) in errs {
                        diagnostic = diagnostic.span_error(err_span, err);
                    }
                }

                diagnostic
            }
        }
    };
}

/// Used by [`diagnostic_error_enum`].
pub struct DiagnosticErrorVariantInfo
{
    pub error: String,
    pub span: Span,
    pub notes: Vec<(String, Span)>,
    pub helps: Vec<(String, Span)>,
    pub errs: Vec<(String, Span)>,
    pub source: Option<Diagnostic>,
}

pub(crate) use diagnostic_error_enum;