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
use std::borrow::Cow;
use std::fmt;

use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};

trait WithSpan {
    fn with_span(self, span: Span) -> Self;
}

impl WithSpan for TokenTree {
    fn with_span(mut self, span: Span) -> Self {
        self.set_span(span);
        self
    }
}

pub(crate) enum Error {
    MissingComponent {
        name: &'static str,
        span_start: Option<Span>,
        span_end: Option<Span>,
    },
    InvalidComponent {
        name: &'static str,
        value: String,
        span_start: Option<Span>,
        span_end: Option<Span>,
    },
    #[cfg(any(feature = "formatting", feature = "parsing"))]
    ExpectedString {
        span_start: Option<Span>,
        span_end: Option<Span>,
    },
    UnexpectedToken {
        tree: TokenTree,
    },
    UnexpectedEndOfInput,
    Custom {
        message: Cow<'static, str>,
        span_start: Option<Span>,
        span_end: Option<Span>,
    },
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::MissingComponent { name, .. } => write!(f, "missing component: {name}"),
            Self::InvalidComponent { name, value, .. } => {
                write!(f, "invalid component: {name} was {value}")
            }
            #[cfg(any(feature = "formatting", feature = "parsing"))]
            Self::ExpectedString { .. } => f.write_str("expected string literal"),
            Self::UnexpectedToken { tree } => write!(f, "unexpected token: {tree}"),
            Self::UnexpectedEndOfInput => f.write_str("unexpected end of input"),
            Self::Custom { message, .. } => f.write_str(message),
        }
    }
}

impl Error {
    fn span_start(&self) -> Span {
        match self {
            Self::MissingComponent { span_start, .. }
            | Self::InvalidComponent { span_start, .. }
            | Self::Custom { span_start, .. } => *span_start,
            #[cfg(any(feature = "formatting", feature = "parsing"))]
            Self::ExpectedString { span_start, .. } => *span_start,
            Self::UnexpectedToken { tree } => Some(tree.span()),
            Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
        }
        .unwrap_or_else(Span::mixed_site)
    }

    fn span_end(&self) -> Span {
        match self {
            Self::MissingComponent { span_end, .. }
            | Self::InvalidComponent { span_end, .. }
            | Self::Custom { span_end, .. } => *span_end,
            #[cfg(any(feature = "formatting", feature = "parsing"))]
            Self::ExpectedString { span_end, .. } => *span_end,
            Self::UnexpectedToken { tree, .. } => Some(tree.span()),
            Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
        }
        .unwrap_or_else(|| self.span_start())
    }

    pub(crate) fn to_compile_error(&self) -> TokenStream {
        let (start, end) = (self.span_start(), self.span_end());

        [
            TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start),
            TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start),
            TokenTree::from(Ident::new("core", start)),
            TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start),
            TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start),
            TokenTree::from(Ident::new("compile_error", start)),
            TokenTree::from(Punct::new('!', Spacing::Alone)).with_span(start),
            TokenTree::from(Group::new(
                Delimiter::Parenthesis,
                TokenStream::from(
                    TokenTree::from(Literal::string(&self.to_string())).with_span(end),
                ),
            ))
            .with_span(end),
        ]
        .iter()
        .cloned()
        .collect()
    }

    /// Like `to_compile_error`, but for use in macros that produce items.
    #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
    pub(crate) fn to_compile_error_standalone(&self) -> TokenStream {
        let end = self.span_end();
        self.to_compile_error()
            .into_iter()
            .chain(std::iter::once(
                TokenTree::from(Punct::new(';', Spacing::Alone)).with_span(end),
            ))
            .collect()
    }
}