Skip to main content

virtue_next/
error.rs

1use crate::generate::PushParseError;
2use crate::generate::StreamBuilder;
3use crate::prelude::*;
4use std::fmt;
5
6/// Errors that can occur while parsing or generator your derive macro.
7#[derive(Debug)]
8pub enum Error {
9    /// The data type at `Span` is unknown. This will be called when [`Parse::new`] is called on anything that is not a `struct` or `enum`.
10    ///
11    /// [`Parse::new`]: enum.Parse.html#method.new
12    UnknownDataType(Span),
13
14    /// The rust syntax is invalid. This can be returned while parsing the Enum or Struct.
15    ///
16    /// This error is assumed to not appear as rustc will do syntax checking before virtue gets access to the [`TokenStream`].
17    /// However this error could still be returned.
18    InvalidRustSyntax {
19        /// The span at which the invalid syntax is found
20        span: Span,
21        /// The expected rust syntax when this parsing occured
22        expected: String,
23    },
24
25    /// Expected an ident at the given span.
26    ExpectedIdent(Span),
27
28    /// Failed to parse the code passed to [`StreamBuilder::push_parsed`].
29    ///
30    /// [`StreamBuilder::push_parsed`]: struct.StreamBuilder.html#method.push_parsed
31    PushParse {
32        /// An optional span. Normally this is `None`, unless `.with_span` is called.
33        span: Option<Span>,
34        /// The internal parse error
35        error: PushParseError,
36    },
37
38    /// A custom error thrown by the developer
39    Custom {
40        /// The error message
41        error: String,
42        /// Optionally the position that the error occurred at
43        span: Option<Span>,
44    },
45}
46
47impl From<PushParseError> for Error {
48    fn from(e: PushParseError) -> Self {
49        Self::PushParse { span: None, error: e }
50    }
51}
52
53impl Error {
54    /// Throw a custom error
55    pub fn custom(s: impl Into<String>) -> Self {
56        Self::Custom {
57            error: s.into(),
58            span: None,
59        }
60    }
61
62    /// Throw a custom error at a given location
63    pub fn custom_at(
64        s: impl Into<String>,
65        span: Span,
66    ) -> Self {
67        Self::Custom {
68            error: s.into(),
69            span: Some(span),
70        }
71    }
72
73    /// Throw a custom error at a given token
74    #[allow(clippy::needless_pass_by_value)]
75    pub fn custom_at_token(
76        s: impl Into<String>,
77        token: TokenTree,
78    ) -> Self {
79        Self::Custom {
80            error: s.into(),
81            span: Some(token.span()),
82        }
83    }
84
85    /// Throw a custom error at a given `Option<TokenTree>`
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if parsing fails.
90    pub fn custom_at_opt_token(
91        s: impl Into<String>,
92        token: Option<TokenTree>,
93    ) -> Self {
94        Self::Custom {
95            error: s.into(),
96            span: token.map(|t| t.span()),
97        }
98    }
99
100    pub(crate) fn wrong_token<T>(
101        token: Option<&TokenTree>,
102        expected: &str,
103    ) -> Result<T> {
104        Err(Self::InvalidRustSyntax {
105            span: token.map_or_else(Span::call_site, TokenTree::span),
106            expected: format!("{expected}, got {token:?}"),
107        })
108    }
109
110    /// Return a new error that is located at the given span
111    #[must_use]
112    #[allow(clippy::match_same_arms)]
113    pub const fn with_span(
114        mut self,
115        new_span: Span,
116    ) -> Self {
117        match &mut self {
118            | Self::UnknownDataType(span) => *span = new_span,
119            | Self::InvalidRustSyntax { span, .. } => *span = new_span,
120            | Self::ExpectedIdent(span) => *span = new_span,
121            | Self::PushParse { span, .. } => {
122                *span = Some(new_span);
123            },
124            | Self::Custom { span, .. } => *span = Some(new_span),
125        }
126
127        self
128    }
129}
130
131// helper functions for the unit tests
132#[cfg(test)]
133impl Error {
134    /// # Errors
135    ///
136    /// Returns an error if the operation fails.
137    /// helper functions for the unit tests
138    pub fn is_unknown_data_type(&self) -> bool {
139        matches!(self, Error::UnknownDataType(_))
140    }
141
142    /// # Errors
143    ///
144    /// Returns an error if the operation fails.
145
146    /// helper functions for the unit tests
147    pub fn is_invalid_rust_syntax(&self) -> bool {
148        matches!(self, Error::InvalidRustSyntax { .. })
149    }
150}
151
152impl fmt::Display for Error {
153    fn fmt(
154        &self,
155        fmt: &mut fmt::Formatter<'_>,
156    ) -> fmt::Result {
157        match self {
158            | Self::UnknownDataType(_) => {
159                write!(fmt, "Unknown data type, only enum and struct are supported")
160            },
161            | Self::InvalidRustSyntax { expected, .. } => {
162                write!(fmt, "Invalid rust syntax, expected {expected}")
163            },
164            | Self::ExpectedIdent(_) => write!(fmt, "Expected ident"),
165            | Self::PushParse { error, .. } => {
166                write!(
167                    fmt,
168                    "Invalid code passed to `StreamBuilder::push_parsed`: {error:?}"
169                )
170            },
171            | Self::Custom { error, .. } => write!(fmt, "{error}"),
172        }
173    }
174}
175
176impl Error {
177    /// Turn this error into a [`TokenStream`] so it shows up as a [`compile_error`] for the user.
178    ///
179    /// # Panics
180    ///
181    /// Panics if an internal invariant is violated.
182    pub fn into_token_stream(self) -> TokenStream {
183        let maybe_span = match &self {
184            | Self::UnknownDataType(span)
185            | Self::ExpectedIdent(span)
186            | Self::InvalidRustSyntax { span, .. } => Some(*span),
187            | Self::Custom { span, .. } | Self::PushParse { span, .. } => *span,
188        };
189        self.throw_with_span(maybe_span.unwrap_or_else(Span::call_site))
190    }
191
192    /// Turn this error into a [`TokenStream`] so it shows up as a [`compile_error`] for the user. The error will be shown at the given `span`.
193    ///
194    /// # Panics
195    ///
196    /// Panics if an internal invariant is violated.
197    #[must_use]
198    pub fn throw_with_span(
199        self,
200        span: Span,
201    ) -> TokenStream {
202        // compile_error!($message)
203        let mut builder = StreamBuilder::new();
204        builder.ident_str("compile_error");
205        builder.punct('!');
206        builder
207            .group(Delimiter::Brace, |b| {
208                b.lit_str(self.to_string());
209                Ok(())
210            })
211            .unwrap();
212        builder.set_span_on_all_tokens(span);
213        builder.stream
214    }
215}