Skip to main content

time_macros/
error.rs

1use std::borrow::Cow;
2use std::fmt;
3
4use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
5
6trait WithSpan {
7    fn with_span(self, span: Span) -> Self;
8}
9
10impl WithSpan for TokenTree {
11    fn with_span(mut self, span: Span) -> Self {
12        self.set_span(span);
13        self
14    }
15}
16
17pub(crate) enum Error {
18    MissingComponent {
19        name: &'static str,
20        span_start: Option<Span>,
21        span_end: Option<Span>,
22    },
23    InvalidComponent {
24        name: &'static str,
25        value: String,
26        span_start: Option<Span>,
27        span_end: Option<Span>,
28    },
29    #[cfg(any(feature = "formatting", feature = "parsing"))]
30    ExpectedString {
31        span_start: Option<Span>,
32        span_end: Option<Span>,
33    },
34    UnexpectedToken {
35        tree: TokenTree,
36    },
37    UnexpectedEndOfInput,
38    #[cfg(any(feature = "formatting", feature = "parsing"))]
39    ByteStringNotPermitted {
40        span_start: Option<Span>,
41        span_end: Option<Span>,
42    },
43    Custom {
44        message: Cow<'static, str>,
45        span_start: Option<Span>,
46        span_end: Option<Span>,
47    },
48}
49
50impl fmt::Display for Error {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::MissingComponent { name, .. } => write!(f, "missing component: {name}"),
54            Self::InvalidComponent { name, value, .. } => {
55                write!(f, "invalid component: {name} was {value}")
56            }
57            #[cfg(any(feature = "formatting", feature = "parsing"))]
58            Self::ExpectedString { .. } => f.write_str("expected string literal"),
59            Self::UnexpectedToken { tree } => write!(f, "unexpected token: {tree}"),
60            Self::UnexpectedEndOfInput => f.write_str("unexpected end of input"),
61            #[cfg(any(feature = "formatting", feature = "parsing"))]
62            Self::ByteStringNotPermitted { .. } => f.write_str("byte strings are not permitted"),
63            Self::Custom { message, .. } => f.write_str(message),
64        }
65    }
66}
67
68impl Error {
69    fn span_start(&self) -> Span {
70        match self {
71            Self::MissingComponent { span_start, .. }
72            | Self::InvalidComponent { span_start, .. }
73            | Self::Custom { span_start, .. } => *span_start,
74            #[cfg(any(feature = "formatting", feature = "parsing"))]
75            Self::ExpectedString { span_start, .. }
76            | Self::ByteStringNotPermitted { span_start, .. } => *span_start,
77            Self::UnexpectedToken { tree } => Some(tree.span()),
78            Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
79        }
80        .unwrap_or_else(Span::mixed_site)
81    }
82
83    fn span_end(&self) -> Span {
84        match self {
85            Self::MissingComponent { span_end, .. }
86            | Self::InvalidComponent { span_end, .. }
87            | Self::Custom { span_end, .. } => *span_end,
88            #[cfg(any(feature = "formatting", feature = "parsing"))]
89            Self::ExpectedString { span_end, .. }
90            | Self::ByteStringNotPermitted { span_end, .. } => *span_end,
91            Self::UnexpectedToken { tree, .. } => Some(tree.span()),
92            Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
93        }
94        .unwrap_or_else(|| self.span_start())
95    }
96
97    pub(crate) fn to_compile_error(&self) -> TokenStream {
98        let (start, end) = (self.span_start(), self.span_end());
99
100        [
101            TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start),
102            TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start),
103            TokenTree::from(Ident::new("core", start)),
104            TokenTree::from(Punct::new(':', Spacing::Joint)).with_span(start),
105            TokenTree::from(Punct::new(':', Spacing::Alone)).with_span(start),
106            TokenTree::from(Ident::new("compile_error", start)),
107            TokenTree::from(Punct::new('!', Spacing::Alone)).with_span(start),
108            TokenTree::from(Group::new(
109                Delimiter::Parenthesis,
110                TokenStream::from(
111                    TokenTree::from(Literal::string(&self.to_string())).with_span(end),
112                ),
113            ))
114            .with_span(end),
115        ]
116        .iter()
117        .cloned()
118        .collect()
119    }
120
121    /// Like `to_compile_error`, but for use in macros that produce items.
122    #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
123    pub(crate) fn to_compile_error_standalone(&self) -> TokenStream {
124        let end = self.span_end();
125        self.to_compile_error()
126            .into_iter()
127            .chain(std::iter::once(
128                TokenTree::from(Punct::new(';', Spacing::Alone)).with_span(end),
129            ))
130            .collect()
131    }
132}