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

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

use crate::format_description::error::InvalidFormatDescription;

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>,
    },
    ExpectedString {
        span_start: Option<Span>,
        span_end: Option<Span>,
    },
    UnexpectedToken {
        tree: TokenTree,
    },
    UnexpectedEndOfInput,
    InvalidFormatDescription {
        error: InvalidFormatDescription,
        span_start: Option<Span>,
        span_end: Option<Span>,
    },
    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: {} was {}", name, value)
            }
            Self::ExpectedString { .. } => f.write_str("expected string"),
            Self::UnexpectedToken { tree } => write!(f, "unexpected token: {}", tree),
            Self::UnexpectedEndOfInput => f.write_str("unexpected end of input"),
            Self::InvalidFormatDescription { error, .. } => error.fmt(f),
            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::ExpectedString { span_start, .. }
            | Self::InvalidFormatDescription { span_start, .. }
            | Self::Custom { 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::ExpectedString { span_end, .. }
            | Self::InvalidFormatDescription { span_end, .. }
            | Self::Custom { 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()
    }
}