use proc_macro2::TokenStream;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacroErrorCode {
ArcM001,
ArcM002,
ArcM003,
ArcM004,
ArcM005,
ArcM006,
ArcM007,
ArcM008,
ArcM009,
ArcM010,
ArcM011,
ArcM012,
ArcM013,
ArcM014,
}
impl MacroErrorCode {
pub fn label(self) -> &'static str {
match self {
Self::ArcM001 => "error[ARC-M001]",
Self::ArcM002 => "error[ARC-M002]",
Self::ArcM003 => "error[ARC-M003]",
Self::ArcM004 => "error[ARC-M004]",
Self::ArcM005 => "error[ARC-M005]",
Self::ArcM006 => "error[ARC-M006]",
Self::ArcM007 => "error[ARC-M007]",
Self::ArcM008 => "error[ARC-M008]",
Self::ArcM009 => "error[ARC-M009]",
Self::ArcM010 => "error[ARC-M010]",
Self::ArcM011 => "error[ARC-M011]",
Self::ArcM012 => "error[ARC-M012]",
Self::ArcM013 => "error[ARC-M013]",
Self::ArcM014 => "error[ARC-M014]",
}
}
}
#[derive(Debug)]
pub struct MacroError {
code: MacroErrorCode,
inner: syn::Error,
}
impl MacroError {
pub fn new(code: MacroErrorCode, span: proc_macro2::Span, message: impl Into<String>) -> Self {
let rendered = format!("{}: {}", code.label(), message.into());
Self {
code,
inner: syn::Error::new(span, rendered),
}
}
pub fn from_syn(code: MacroErrorCode, err: syn::Error) -> Self {
let span = err.span();
let rendered = format!("{}: {}", code.label(), err);
Self {
code,
inner: syn::Error::new(span, rendered),
}
}
pub fn code(&self) -> MacroErrorCode {
self.code
}
pub fn to_compile_error(&self) -> TokenStream {
self.inner.to_compile_error()
}
}
impl From<MacroError> for TokenStream {
fn from(err: MacroError) -> TokenStream {
err.to_compile_error()
}
}
pub type MacroResult = Result<TokenStream, MacroError>;
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::Span;
#[test]
fn labels_are_stable() {
assert_eq!(MacroErrorCode::ArcM001.label(), "error[ARC-M001]");
assert_eq!(MacroErrorCode::ArcM002.label(), "error[ARC-M002]");
assert_eq!(MacroErrorCode::ArcM003.label(), "error[ARC-M003]");
}
#[test]
fn codes_are_copy_eq() {
let a = MacroErrorCode::ArcM001;
let b = a;
assert_eq!(a, b);
}
#[test]
fn new_error_renders_code_and_message() {
let err = MacroError::new(MacroErrorCode::ArcM002, Span::call_site(), "bad attribute");
let tokens_str = err.to_compile_error().to_string();
assert!(
tokens_str.contains("ARC-M002"),
"expected ARC-M002 in output, got: {tokens_str}"
);
assert!(
tokens_str.contains("bad attribute"),
"expected 'bad attribute' in output, got: {tokens_str}"
);
}
#[test]
fn from_syn_preserves_span_and_adds_code() {
let syn_err = syn::Error::new(Span::call_site(), "unexpected token");
let err = MacroError::from_syn(MacroErrorCode::ArcM001, syn_err);
let tokens_str = err.to_compile_error().to_string();
assert!(
tokens_str.contains("ARC-M001"),
"expected ARC-M001 in output, got: {tokens_str}"
);
assert!(
tokens_str.contains("unexpected token"),
"expected 'unexpected token' in output, got: {tokens_str}"
);
}
}