#![allow(clippy::missing_errors_doc)]
use std::convert::Infallible;
use std::fmt::{Debug, Display};
use std::mem;
use std::ops::{Add, AddAssign, Range};
#[cfg(feature = "darling")]
use darling_core::Error as DarlingError;
use proc_macro2::{Span, TokenStream};
use quote::{quote_spanned, ToTokens};
#[cfg(feature = "syn1")]
use syn1::Error as Syn1Error;
#[cfg(feature = "syn2")]
use syn2::Error as Syn2Error;
#[cfg(doc)]
use crate::MacroOutput;
use crate::{to_tokens_span_range, SpanRanged};
pub type Result<T = TokenStream, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub struct SilentError;
#[derive(Debug)]
#[must_use]
pub struct Error(Vec<Box<dyn ToTokensError>>);
#[cfg(feature = "syn1")]
impl From<Syn1Error> for Error {
fn from(error: Syn1Error) -> Self {
Self::from(error)
}
}
#[cfg(feature = "syn2")]
impl From<Syn2Error> for Error {
fn from(error: Syn2Error) -> Self {
Self::from(error)
}
}
#[cfg(feature = "darling")]
impl From<DarlingError> for Error {
fn from(error: DarlingError) -> Self {
Self::from(error)
}
}
impl From<ErrorMessage> for Error {
fn from(error: ErrorMessage) -> Self {
Self::from(error)
}
}
impl From<SilentError> for Error {
fn from(_: SilentError) -> Self {
Self(Vec::new())
}
}
impl<T: ToTokensError + 'static> Add<T> for Error {
type Output = Error;
fn add(self, rhs: T) -> Self::Output {
self.join(rhs)
}
}
impl<T: ToTokensError + 'static> AddAssign<T> for Error {
fn add_assign(&mut self, rhs: T) {
self.push(rhs);
}
}
impl Error {
pub fn from(error: impl ToTokensError + 'static) -> Self {
Self(vec![Box::new(error)])
}
pub fn push(&mut self, error: impl ToTokensError + 'static) {
self.0.push(Box::new(error));
}
}
impl<I: ToTokensError + 'static> Extend<I> for Error {
fn extend<T: IntoIterator<Item = I>>(&mut self, iter: T) {
self.0.extend(
iter.into_iter()
.map(|i| Box::new(i) as Box<dyn ToTokensError>),
);
}
}
#[derive(Debug)]
#[must_use]
pub struct ErrorMessage {
span: Range<Span>,
msg: String,
attachments: Vec<(&'static str, String)>,
}
impl Display for ErrorMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.msg.trim_end())?;
if !self.attachments.is_empty() {
write!(f, "\n\n")?;
}
for (label, attachment) in &self.attachments {
let mut attachment = attachment.lines();
writeln!(
f,
" = {label}: {}",
attachment.next().expect("should return at least one line")
)?;
for line in attachment {
writeln!(f, " {1:2$} {}", line, "", label.len())?;
}
}
Ok(())
}
}
impl ToTokensError for ErrorMessage {
fn to_tokens(&self, tokens: &mut TokenStream) {
let msg = self.to_string();
let msg = quote_spanned!(self.span.end => {#msg});
quote_spanned! {self.span.start =>
::core::compile_error! #msg
}
.to_tokens(tokens);
}
}
#[cfg(feature = "syn1")]
impl From<ErrorMessage> for Syn1Error {
fn from(value: ErrorMessage) -> Self {
Self::new_spanned(value.to_token_stream(), value)
}
}
#[cfg(feature = "syn2")]
impl From<ErrorMessage> for Syn2Error {
fn from(value: ErrorMessage) -> Self {
Self::new_spanned(value.to_token_stream(), value)
}
}
impl<T: ToTokensError + 'static> Add<T> for ErrorMessage {
type Output = Error;
fn add(self, rhs: T) -> Self::Output {
self.join(rhs)
}
}
impl ErrorMessage {
pub fn new(span: impl SpanRanged, msg: impl Display) -> Self {
Self {
span: span.span_range(),
msg: msg.to_string(),
attachments: Vec::new(),
}
}
pub fn spanned(tokens: impl ToTokens, msg: impl Display) -> Self {
Self {
span: to_tokens_span_range(tokens),
msg: msg.to_string(),
attachments: Vec::new(),
}
}
pub fn call_site(msg: impl Display) -> Self {
Self::new(Span::call_site(), msg)
}
pub fn attachment(mut self, label: &'static str, msg: impl Display) -> Self {
self.attachments.push((label, msg.to_string()));
self
}
pub fn error(self, msg: impl Display) -> Self {
self.attachment("error", msg)
}
pub fn warning(self, msg: impl Display) -> Self {
self.attachment("warning", msg)
}
pub fn note(self, msg: impl Display) -> Self {
self.attachment("note", msg)
}
pub fn help(self, msg: impl Display) -> Self {
self.attachment("help", msg)
}
}
pub trait Attachment: Sized {
#[must_use]
fn attachment(self, label: &'static str, msg: impl Display) -> Self;
}
impl Attachment for ErrorMessage {
fn attachment(mut self, label: &'static str, msg: impl Display) -> Self {
self.attachments.push((label, msg.to_string()));
self
}
}
#[derive(Default, Debug)]
pub struct Emitter(Vec<Box<dyn ToTokensError>>);
impl<T: ToTokensError + 'static> AddAssign<T> for Emitter {
fn add_assign(&mut self, rhs: T) {
self.emit(rhs);
}
}
impl Emitter {
#[must_use]
pub fn new() -> Self {
Emitter(Vec::new())
}
pub(crate) fn to_tokens(&self, tokens: &mut TokenStream) {
for error in &self.0 {
error.to_tokens(tokens);
}
}
pub fn emit(&mut self, error: impl ToTokensError + 'static) {
self.0.push(Box::new(error));
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn into_result(&mut self) -> Result<(), Error> {
if self.is_empty() {
Ok(())
} else {
Err(Error(mem::take(&mut self.0)))
}
}
}
impl<I: ToTokensError + 'static> Extend<I> for Emitter {
fn extend<T: IntoIterator<Item = I>>(&mut self, iter: T) {
self.0.extend(
iter.into_iter()
.map(|i| Box::new(i) as Box<dyn ToTokensError>),
);
}
}
pub trait ToTokensError: Debug {
fn to_tokens(&self, tokens: &mut TokenStream);
fn to_token_stream(&self) -> TokenStream {
let mut tokens = TokenStream::new();
self.to_tokens(&mut tokens);
tokens
}
fn into_token_stream(self) -> TokenStream
where
Self: Sized,
{
self.to_token_stream()
}
}
pub trait JoinToTokensError {
fn join(self, error: impl ToTokensError + 'static) -> Error;
}
impl<T: Sized + ToTokensError + 'static> JoinToTokensError for T {
fn join(self, error: impl ToTokensError + 'static) -> Error {
let mut this = Error::from(self);
this.push(error);
this
}
}
impl ToTokensError for Infallible {
fn to_tokens(&self, _: &mut TokenStream) {
unreachable!()
}
}
#[cfg(feature = "syn1")]
impl ToTokensError for Syn1Error {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.to_compile_error().to_tokens(tokens);
}
}
#[cfg(feature = "syn")]
impl ToTokensError for Syn2Error {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.to_compile_error().to_tokens(tokens);
}
}
#[cfg(feature = "darling")]
impl ToTokensError for DarlingError {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.clone().write_errors().to_tokens(tokens);
}
}
impl ToTokensError for Error {
fn to_tokens(&self, tokens: &mut TokenStream) {
for error in &self.0 {
error.to_tokens(tokens);
}
}
}
impl ToTokensError for SilentError {
fn to_tokens(&self, _: &mut TokenStream) {}
}
pub trait ResultExt<T, E>: Sized {
fn context(self, error: impl ToTokensError + 'static) -> Result<T, Error> {
self.context_with(|| error)
}
fn context_with<C: ToTokensError + 'static>(
self,
error: impl FnOnce() -> C,
) -> Result<T, Error>;
#[must_use]
fn attachment(self, label: &'static str, msg: impl Display) -> Self
where
E: Attachment;
#[must_use]
fn error(self, msg: impl Display) -> Self
where
E: Attachment,
{
self.attachment("error", msg)
}
#[must_use]
fn warning(self, msg: impl Display) -> Self
where
E: Attachment,
{
self.attachment("warning", msg)
}
#[must_use]
fn note(self, msg: impl Display) -> Self
where
E: Attachment,
{
self.attachment("note", msg)
}
#[must_use]
fn help(self, msg: impl Display) -> Self
where
E: Attachment,
{
self.attachment("help", msg)
}
}
impl<T, E: ToTokensError + 'static> ResultExt<T, E> for Result<T, E> {
fn context_with<C: ToTokensError + 'static>(
self,
error: impl FnOnce() -> C,
) -> Result<T, Error> {
self.map_err(|e| {
let mut e = Error::from(e);
e.push(error());
e
})
}
fn attachment(self, label: &'static str, msg: impl Display) -> Result<T, E>
where
E: Attachment,
{
self.map_err(|e| e.attachment(label, msg))
}
}
#[cfg(test)]
mod test {
use proc_macro_utils::assert_tokens;
use super::*;
#[test]
fn error_message() {
let error_message = ErrorMessage::new(Span::call_site(), "test message")
.help("try to call your dog")
.note("you could use the banana phone")
.warning("be careful")
.error("you cannot reach them");
assert_tokens! {error_message.to_token_stream(), {
::core::compile_error! {
"test message\n\n = help: try to call your dog\n = note: you could use the banana phone\n = warning: be careful\n = error: you cannot reach them\n"
}
}}
}
}