use crate::{
multi::{abort_now, push_error},
ResultExt,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote_spanned, ToTokens};
use std::{
fmt::{Display, Formatter},
ops::{Deref, DerefMut},
};
#[macro_export]
macro_rules! macro_error {
($err:expr) => {{
$crate::MacroError::from($err)
}};
($span:expr, $fmt:expr, $($args:expr),+ ; $help:ident = $help_fmt:expr, $($help_args:expr),+) => {{
let msg = format!($fmt, $($args),*);
let help = format!($help_fmt, $($help_args),*);
let span = $span.into();
$crate::MacroError::with_help_abbr(span, msg, help, stringify!($help))
}};
($span:expr, $fmt:expr, $($args:expr),+ ; $help:ident = $help_msg:expr) => {{
let msg = format!($fmt, $($args),*);
let help = $help_msg.to_string();
let span = $span.into();
$crate::MacroError::with_help_abbr(span, msg, help, stringify!($help))
}};
($span:expr, $msg:expr ; $help:ident = $help_msg:expr, $($help_args:expr),+) => {{
let msg = $msg.to_string();
let help = format!($help_fmt, $($help_args),*);
let span = $span.into();
$crate::MacroError::with_help_abbr(span, msg, help, stringify!($help))
}};
($span:expr, $msg:expr ; $help:ident = $help_msg:expr) => {{
let msg = $msg.to_string();
let help = $help_msg.to_string();
let span = $span.into();
$crate::MacroError::with_help_abbr(span, msg, help, stringify!($help))
}};
($span:expr, $fmt:expr, $($args:expr),+) => {{
let msg = format!($fmt, $($args),*);
let span = $span.into();
$crate::MacroError::new(span, msg)
}};
($span:expr, $msg:expr) => {{
$crate::MacroError::new($span.into(), $msg.to_string())
}};
}
#[macro_export]
macro_rules! abort {
($($tts:tt)*) => {{
$crate::macro_error!($($tts)*).abort()
}};
}
#[macro_export]
macro_rules! abort_call_site {
($($tts:tt)*) => {{
let span = $crate::proc_macro2::Span::call_site();
$crate::macro_error!(span, $($tts)*).abort()
}};
}
#[derive(Debug)]
pub struct MacroError {
pub(crate) span: Span,
pub(crate) msg: String,
pub(crate) help: Option<String>,
pub(crate) help_word: &'static str,
}
impl MacroError {
pub fn new(span: Span, msg: String) -> Self {
MacroError {
span,
msg,
help: None,
help_word: "help",
}
}
pub fn with_help(span: Span, msg: String, help: String) -> Self {
MacroError {
span,
msg,
help: Some(help),
help_word: "help",
}
}
#[doc(hidden)]
pub fn with_help_abbr(span: Span, msg: String, help: String, help_word: &'static str) -> Self {
MacroError {
span,
msg,
help: Some(help),
help_word,
}
}
pub fn call_site(msg: String) -> Self {
MacroError::new(Span::call_site(), msg)
}
pub fn call_site_help(msg: String, help: String) -> Self {
MacroError::with_help(Span::call_site(), msg, help)
}
pub fn set_span(&mut self, span: Span) -> Span {
std::mem::replace(&mut self.span, span)
}
pub fn span(&self) -> Span {
self.span
}
pub fn abort(self) -> ! {
push_error(self);
abort_now()
}
pub fn emit(self) {
push_error(self);
}
}
impl From<syn::Error> for MacroError {
fn from(e: syn::Error) -> Self {
MacroError::new(e.span(), e.to_string())
}
}
impl From<String> for MacroError {
fn from(msg: String) -> Self {
MacroError::call_site(msg)
}
}
impl From<&str> for MacroError {
fn from(msg: &str) -> Self {
MacroError::call_site(msg.into())
}
}
impl ToTokens for MacroError {
fn to_tokens(&self, ts: &mut TokenStream) {
let span = &self.span;
let msg = syn::LitStr::new(&self.to_string(), *span);
ts.extend(quote_spanned!(*span=> compile_error!(#msg); ));
}
}
impl Display for MacroError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
fn ensure_double_lf(f: &mut Formatter, s: &str) -> std::fmt::Result {
if s.ends_with("\n\n") {
Display::fmt(s, f)
} else if s.ends_with('\n') {
write!(f, "{}\n", s)
} else {
write!(f, "{}\n\n", s)
}
}
let MacroError {
ref msg,
ref help,
ref help_word,
..
} = *self;
if let Some(help) = help {
ensure_double_lf(f, msg)?;
write!(f, " {}: ", help_word)?;
ensure_double_lf(f, help)
} else {
Display::fmt(msg, f)
}
}
}
impl<T, E: Into<MacroError>> ResultExt for Result<T, E> {
type Ok = T;
fn unwrap_or_abort(self) -> T {
match self {
Ok(res) => res,
Err(e) => e.into().abort(),
}
}
fn expect_or_abort(self, message: &str) -> T {
match self {
Ok(res) => res,
Err(e) => {
let e = e.into();
let span = e.span;
let msg = e.to_string();
abort!(span, "{}: {}", message, msg);
}
}
}
}
impl Deref for MacroError {
type Target = str;
fn deref(&self) -> &str {
&self.msg
}
}
impl DerefMut for MacroError {
fn deref_mut(&mut self) -> &mut str {
&mut self.msg
}
}