#![warn(missing_docs)]
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use std::fmt;
#[derive(Debug, Clone)]
pub struct Error {
inner: Option<(Span, String)>,
overflow: Option<Vec<(Span, String)>>,
}
impl Error {
#[must_use]
#[inline]
pub fn new(span: Span, message: impl Into<String>) -> Self {
Self {
inner: Some((span, message.into())),
overflow: None,
}
}
#[must_use]
#[inline]
pub fn empty() -> Self {
Self {
inner: None,
overflow: None,
}
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_none()
}
#[must_use]
#[inline]
pub fn to_compile_error(&self) -> TokenStream {
let mut tokens = TokenStream::new();
let iter = self.inner.iter().chain(self.overflow.iter().flatten());
for (span, msg) in iter {
let ident = Ident::new("compile_error", *span);
let mut bang = Punct::new('!', Spacing::Alone);
bang.set_span(*span);
let mut lit = Literal::string(msg);
lit.set_span(*span);
let mut group = Group::new(Delimiter::Parenthesis, TokenTree::from(lit).into());
group.set_span(*span);
let mut semi = Punct::new(';', Spacing::Alone);
semi.set_span(*span);
tokens.extend([
TokenTree::Ident(ident),
TokenTree::Punct(bang),
TokenTree::Group(group),
TokenTree::Punct(semi),
]);
}
tokens
}
#[inline]
pub fn combine(&mut self, mut other: Self) {
match (&mut self.inner, other.inner.take()) {
(Some(_), Some(msg)) => {
self.overflow.get_or_insert_with(Vec::new).push(msg);
}
(inner @ None, Some(msg)) => {
*inner = Some(msg);
}
(_, None) => {}
}
if let Some(v) = other.overflow.take() {
let out = self.overflow.get_or_insert_with(Vec::new);
out.extend(v);
}
}
#[must_use]
pub fn from_token_stream(tokens: TokenStream) -> Self {
let mut combined_error: Option<Self> = None;
let mut iter = tokens.into_iter().peekable();
while let Some(token) = iter.next() {
if let TokenTree::Ident(ident) = &token {
if ident == "compile_error" {
let error_span = ident.span();
let has_bang =
matches!(iter.next(), Some(TokenTree::Punct(p)) if p.as_char() == '!');
if has_bang {
if let Some(TokenTree::Group(group)) = iter.next() {
let message = Self::extract_message_from_group(&group);
let new_error = Self::new(error_span, message);
match combined_error.as_mut() {
Some(e) => e.combine(new_error),
None => combined_error = Some(new_error),
}
}
}
}
}
}
combined_error.unwrap_or_else(|| {
Self::new(
Span::call_site(),
"An unexpected error occurred during parsing",
)
})
}
fn extract_message_from_group(group: &proc_macro2::Group) -> String {
group
.stream()
.into_iter()
.find_map(|tt| {
if let TokenTree::Literal(lit) = tt {
let s = lit.to_string();
if s.starts_with('"') && s.ends_with('"') {
return Some(s[1..s.len() - 1].to_string());
}
}
None
})
.unwrap_or_else(|| "unknown error message".to_string())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for (_, msg) in self.inner.iter().chain(self.overflow.iter().flatten()) {
if !first {
f.write_str("\n")?;
}
f.write_str(msg)?;
first = false;
}
Ok(())
}
}
impl std::error::Error for Error {}
impl Extend<Error> for Error {
fn extend<I: IntoIterator<Item = Error>>(&mut self, iter: I) {
for err in iter {
self.combine(err);
}
}
}
impl FromIterator<Error> for Error {
fn from_iter<I: IntoIterator<Item = Error>>(iter: I) -> Self {
let mut combined = Error::empty();
for err in iter {
combined.combine(err);
}
combined
}
}
#[must_use]
#[inline]
pub fn error(span: Span, message: impl Into<String>) -> TokenStream {
Error::new(span, message).to_compile_error()
}