#![forbid(unsafe_code)]
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::let_underscore_untyped)]
use crate::parser::{Error, ErrorSource, VariantType};
use myn::utils::spanned_error;
use proc_macro::{Span, TokenStream};
use std::{fmt::Write as _, rc::Rc, str::FromStr as _};
mod parser;
#[allow(clippy::too_many_lines)]
#[proc_macro_derive(Error, attributes(error, from, source, no_display))]
pub fn derive_error(input: TokenStream) -> TokenStream {
let ast = match Error::parse(input) {
Ok(ast) => ast,
Err(err) => return err,
};
#[cfg(feature = "std")]
let std_crate = "std";
#[cfg(not(feature = "std"))]
let std_crate = "core";
let name = &ast.name;
let error_matches = ast
.variants
.iter()
.filter_map(|v| match &v.source {
ErrorSource::From(index) | ErrorSource::Source(index) => {
let name = &v.name;
Some(match &v.ty {
VariantType::Unit => format!("Self::{name} => None,"),
VariantType::Tuple => {
let index_num: usize = index.parse().unwrap_or_default();
let fields = (0..v.fields.len())
.map(|i| if i == index_num { "field," } else { "_," })
.collect::<String>();
format!("Self::{name}({fields}) => Some(field),")
}
VariantType::Struct => {
format!("Self::{name} {{ {index}, ..}} => Some({index}),")
}
})
}
ErrorSource::None => None,
})
.collect::<String>();
let display_impl = if ast.no_display {
String::new()
} else {
let display = ast.variants.iter().map(|v| {
let name = &v.name;
let display = &v.display;
if display.is_empty() {
return Err(name);
}
let display_fields =
v.display_fields
.iter()
.fold(String::new(), |mut fields, field| {
let _ = write!(fields, "{field},");
fields
});
Ok(match &v.ty {
VariantType::Unit => format!("Self::{name} => write!(f, {display:?}),"),
VariantType::Tuple => {
let fields = (0..v.fields.len()).fold(String::new(), |mut fields, i| {
if v.display_fields.contains(&Rc::from(format!("field_{i}"))) {
let _ = write!(fields, "field_{i},");
} else {
let _ = fields.write_str("_,");
}
fields
});
format!("Self::{name}({fields}) => write!(f, {display:?}, {display_fields}),")
}
VariantType::Struct => {
format!(
"Self::{name} {{ {display_fields} .. }} => \
write!(f, {display:?}, {display_fields}),"
)
}
})
});
let mut display_matches = String::new();
for res in display {
match res {
Err(name) => {
return spanned_error("Required error message is missing", name.span());
}
Ok(msg) => display_matches.push_str(&msg),
}
}
let display_matches = if display_matches.is_empty() {
String::from("Ok(())")
} else {
format!("match self {{ {display_matches} }}")
};
format!(
r#"impl ::{std_crate}::fmt::Display for {name} {{
fn fmt(&self, f: &mut ::{std_crate}::fmt::Formatter<'_>) ->
::{std_crate}::result::Result<(), ::{std_crate}::fmt::Error>
{{
{display_matches}
}}
}}"#
)
};
let from_impls = ast
.variants
.into_iter()
.filter_map(|v| match v.source {
ErrorSource::From(index) => {
let variant_name = v.name;
let from_ty = &v.fields[&index];
let body = if v.ty == VariantType::Tuple {
format!(r#"Self::{variant_name}(value)"#)
} else {
format!(r#"Self::{variant_name} {{ {index}: value }}"#)
};
Some(format!(
r#"impl ::{std_crate}::convert::From<{from_ty}> for {name} {{
fn from(value: {from_ty}) -> Self {{
{body}
}}
}}"#
))
}
_ => None,
})
.collect::<String>();
let code = TokenStream::from_str(&format!(
r#"
impl ::{std_crate}::error::Error for {name} {{
fn source(&self) -> Option<&(dyn ::{std_crate}::error::Error + 'static)> {{
match self {{
{error_matches}
_ => None,
}}
}}
}}
{display_impl}
{from_impls}
"#
));
match code {
Ok(stream) => stream,
Err(err) => spanned_error(err.to_string(), Span::call_site()),
}
}