use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, TokenStreamExt};
use syn::fold::{self, Fold};
use syn::token::Comma;
use syn::{
parse_macro_input, parse_quote, Attribute, Expr, ExprMatch, FieldValue, Fields, Ident, Item,
ItemFn, ItemStruct, LitStr, ReturnType, Type, Variant,
};
struct ContextMsg {
m: LitStr,
rettype: Box<Type>,
}
impl ContextMsg {
fn new(m: LitStr, rettype: Box<Type>) -> Self {
ContextMsg { m, rettype }
}
}
impl Fold for ContextMsg {
fn fold_expr(&mut self, e: Expr) -> Expr {
match e {
Expr::Return(mut rexpr) => {
match rexpr.expr {
Some(ex) => {
let m = &self.m;
let rettype = &self.rettype;
rexpr.expr = Some(
parse_quote!(hb_error::ConvertInto::<#rettype>::convert(#ex).map_err(|er| er.make_inner().msg(#m))),
);
}
None => (),
}
fold::fold_expr(self, Expr::Return(rexpr))
}
Expr::Try(mut texpr) => {
let ex = texpr.expr;
let m = &self.m;
let rettype = &self.rettype;
texpr.expr = parse_quote!(hb_error::ConvertInto::<#rettype>::convert(#ex).map_err(|er| er.make_inner().msg(#m)));
Expr::Try(texpr)
}
_ => fold::fold_expr(self, e),
}
}
}
#[proc_macro_attribute]
pub fn context(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let mut message;
if let ReturnType::Type(_, r) = &input.sig.output {
message = ContextMsg::new(parse_macro_input!(args as LitStr), r.clone());
} else {
return TokenStream::from(quote! {#input});
}
let mut output = message.fold_item_fn(input);
let block = output.block.clone();
let msg = message.m.clone();
let rettype = message.rettype.clone();
output.block = parse_quote! {
{
#[allow(unreachable_code)]
let ret: #rettype = {
#[warn(unreachable_code)]
#block
};
#[allow(unreachable_code)]
ret.map_err(|er| er.make_inner().msg(#msg))
}
};
TokenStream::from(quote! {#output})
}
struct Converter {
rettype: Box<Type>,
}
impl Fold for Converter {
fn fold_expr(&mut self, e: Expr) -> Expr {
match e {
Expr::Return(mut rexpr) => {
match rexpr.expr {
Some(ex) => {
let rettype = &self.rettype;
rexpr.expr =
Some(parse_quote!(hb_error::ConvertInto::<#rettype>::convert(#ex)));
}
None => (),
}
fold::fold_expr(self, Expr::Return(rexpr))
}
Expr::Try(mut texpr) => {
let ex = texpr.expr;
let rettype = &self.rettype;
texpr.expr = parse_quote!(hb_error::ConvertInto::<#rettype>::convert(#ex));
Expr::Try(texpr)
}
_ => fold::fold_expr(self, e),
}
}
}
#[proc_macro_attribute]
pub fn convert_error(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let mut message;
if let ReturnType::Type(_, r) = &input.sig.output {
message = Converter { rettype: r.clone() };
} else {
return TokenStream::from(quote! {#input});
}
let output = message.fold_item_fn(input);
TokenStream::from(quote! {#output})
}
#[proc_macro_attribute]
pub fn hberror(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
let mut output_struct = input.clone();
let ident = input.ident;
let ident_source = Ident::new(&format!("{}Source", ident), Span::call_site());
let vis = input.vis;
let mut has_source_enum = false;
let mut enum_variants = syn::punctuated::Punctuated::<Variant, Comma>::new();
let mut new_fields = syn::punctuated::Punctuated::<FieldValue, Comma>::new();
let mut enum_display_match = ExprMatch {
attrs: vec![],
match_token: Default::default(),
expr: Box::new(parse_quote!(self)),
brace_token: Default::default(),
arms: vec![],
};
let mut source_from_impl_items: Vec<Item> = vec![];
let mut custom_fields = vec![];
match &input.fields {
Fields::Named(namedfields) => {
namedfields.named.iter().for_each(|f| {
let mut handled = false;
for a in &f.attrs {
if a.path.is_ident("Source") {
let mut cleaned_field = f.clone();
let mut cleaned_attrs : Vec<Attribute> = vec![];
f.attrs
.iter()
.filter(|a| !a.path.is_ident("Source"))
.for_each(|a| cleaned_attrs.push(a.clone()));
cleaned_field.attrs = cleaned_attrs;
let ty = f.ty.clone();
let f_ident = f.ident.clone().unwrap();
enum_variants.push(Variant {
ident: f.ident.clone().unwrap(),
attrs: vec![],
fields: Fields::Unnamed(parse_quote!((#ty))),
discriminant: None,
});
enum_display_match
.arms
.push(parse_quote!(#ident_source::#f_ident(e) => write!(f, "\n...source error {}...{}",stringify!(#f_ident), e)));
source_from_impl_items.push(parse_quote!(impl From<#ty> for #ident {
fn from(e: #ty) -> #ident {
#ident::new().source(#ident_source::#f_ident(e))
}
}));
has_source_enum = true;
handled = true;
break;
} else if a.path.is_ident("Default") {
let mut cleaned_field = f.clone();
let mut cleaned_attrs : Vec<Attribute> = vec![];
f.attrs
.iter()
.filter(|a| !a.path.is_ident("Default"))
.for_each(|a| cleaned_attrs.push(a.clone()));
cleaned_field.attrs = cleaned_attrs;
custom_fields.push((cleaned_field, Some(a.tokens.clone())));
handled = true;
break;
}
}
if !handled {
custom_fields.push((f.clone(), None));
}
});
}
Fields::Unnamed(_) => (),
Fields::Unit => (),
};
if has_source_enum {
enum_variants.push(Variant {
ident: parse_quote!(None),
attrs: vec![],
fields: Fields::Unit,
discriminant: None,
});
enum_display_match
.arms
.push(parse_quote!(#ident_source::None => Ok(())))
}
new_fields.push(parse_quote!(msg: String::new()));
new_fields.push(parse_quote!(inner_msgs: vec![]));
let mut final_fields = Fields::Named(match has_source_enum {
true => {
new_fields.push(parse_quote!(source: #ident_source::None));
parse_quote! {
{
msg: String,
inner_msgs: Vec<String>,
source: #ident_source,
}
}
}
false => parse_quote! {
{
msg: String,
inner_msgs: Vec<String>,
}
},
});
match &mut final_fields {
Fields::Named(ref mut named_fields) => {
for (f, toks) in custom_fields {
let f_ident = f.ident.clone();
named_fields.named.push(f);
match toks {
Some(tok) => new_fields.push(parse_quote!(#f_ident: #tok)),
None => new_fields.push(parse_quote!(#f_ident: Default::default())),
}
}
}
_ => panic!("should not happen"),
}
let mut msg_args = syn::punctuated::Punctuated::<Expr, Comma>::new();
msg_args.push(parse_quote!(f));
match syn::parse::<LitStr>(args) {
Err(_) => {
if has_source_enum {
msg_args.push(parse_quote!("{}{}{}"));
msg_args.push(parse_quote!(self.msg));
msg_args.push(parse_quote!(self.inner_msgs.join("\n...because...")));
msg_args.push(parse_quote!(self.source));
} else {
msg_args.push(parse_quote!("{}{}"));
msg_args.push(parse_quote!(self.msg));
msg_args.push(parse_quote!(self.inner_msgs.join("\n...because...")));
}
}
Ok(litstr) => {
let str = litstr.value();
let mut brace_contents: Vec<String> = vec![];
let mut in_brace = false;
let mut buf = String::new();
let mut out_str = String::new();
for c in str.chars() {
if c == '{' {
in_brace = true;
out_str.push(c);
} else if c == '}' {
if buf.len() > 0 {
brace_contents.push(buf);
buf = String::new();
}
in_brace = false;
out_str.push(c);
} else if in_brace {
buf.push(c);
} else {
out_str.push(c);
}
}
for content in brace_contents {
msg_args
.push(syn::parse_str::<Expr>(&content).expect(
format!("cannot convert {} into an expression.", content).as_str(),
));
}
if has_source_enum {
msg_args.push(parse_quote!(self.source));
out_str.push_str("{}")
}
msg_args.insert(1, parse_quote!(#out_str));
}
}
output_struct.fields = final_fields;
let main_output = quote!(
#output_struct
impl ErrorContext for #ident {
fn make_inner(mut self) -> #ident {
self.inner_msgs.push(self.msg);
self.msg = String::new();
self
}
fn msg<T: Into<String>>(mut self, msg: T) -> #ident {
self.msg = msg.into();
self
}
}
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(#msg_args)
}
}
impl std::fmt::Debug for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(#msg_args)
}
}
);
let final_output = match has_source_enum {
true => {
let mut out = quote!(
#main_output
impl #ident {
#vis fn new() -> #ident {
#ident {
#new_fields
}
}
#vis fn source(mut self, s: #ident_source) -> #ident {
self.source = s;
self
}
}
#vis enum #ident_source {
#enum_variants
}
impl std::fmt::Display for #ident_source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
#enum_display_match
}
}
impl std::fmt::Debug for #ident_source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
#enum_display_match
}
}
impl std::default::Default for #ident_source {
fn default() -> Self { #ident_source::None }
}
);
out.append_all(source_from_impl_items);
out
}
false => {
parse_quote!(
#main_output
impl #ident {
#vis fn new() -> #ident {
#ident {
#new_fields
}
}
}
)
}
};
final_output.into()
}