use proc_macro2::TokenStream;
use quote::quote;
use syn::{Attribute, Meta, Token, Visibility, punctuated::Punctuated, spanned::Spanned};
use crate::{
messages::{
DISPLAY_MUST_IN_META_LIST, EXPECTED_IDENT, MODULE_MUST_IN_PATH, VIS_MUST_IN_META_LIST,
specified_multiple_times, unknown_single_attr,
},
types::{TypeAttr, TypeDisplayAttr, VariantAttr},
};
pub(crate) fn parse_type_attr(attrs: &[Attribute]) -> syn::Result<TypeAttr> {
fn inner(
attr: &Attribute,
display: &mut TypeDisplayAttr,
vis: &mut Option<Visibility>,
module: &mut bool,
errors: &mut Vec<syn::Error>,
) {
if !attr.path().is_ident("error2") {
return;
}
let nested = match attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
Ok(o) => o,
Err(e) => {
errors.push(e);
return;
}
};
for meta in nested {
let path = meta.path();
let Some(path_ident) = path.get_ident() else {
errors.push(syn::Error::new(path.span(), EXPECTED_IDENT));
continue;
};
if path_ident == "display" {
let list = match meta {
Meta::List(meta_list) => meta_list,
Meta::Path(_) | Meta::NameValue(_) => {
errors.push(syn::Error::new(meta.span(), DISPLAY_MUST_IN_META_LIST));
continue;
}
};
if !display.is_none() {
errors.push(syn::Error::new(
list.span(),
specified_multiple_times("display"),
));
continue;
}
*display = TypeDisplayAttr::Enabled {
meta_span: list.span(),
tokens: list.tokens,
}
} else if path_ident == "vis" {
let list = match meta {
Meta::List(meta_list) => meta_list,
Meta::Path(_) | Meta::NameValue(_) => {
errors.push(syn::Error::new(meta.span(), VIS_MUST_IN_META_LIST));
continue;
}
};
if vis.is_some() {
errors.push(syn::Error::new(
list.span(),
specified_multiple_times("vis"),
));
continue;
}
match list.parse_args::<Visibility>() {
Ok(v) => *vis = Some(v),
Err(e) => {
errors.push(e);
}
}
} else if path_ident == "module" {
let path = match meta {
Meta::Path(path) => path,
Meta::List(_) | Meta::NameValue(_) => {
errors.push(syn::Error::new(meta.span(), MODULE_MUST_IN_PATH));
continue;
}
};
if *module {
errors.push(syn::Error::new(
path.span(),
specified_multiple_times("module"),
));
continue;
} else {
*module = true;
}
} else {
errors.push(syn::Error::new(
path_ident.span(),
format!(
"unknown attribute `{}`, only `display`, `vis` and `module` are supported",
path_ident
),
));
}
}
}
let mut display = TypeDisplayAttr::None;
let mut vis: Option<Visibility> = None;
let mut module = false;
let mut errors = Vec::new();
attrs
.iter()
.for_each(|attr| inner(attr, &mut display, &mut vis, &mut module, &mut errors));
if let Some(e) = errors.into_iter().reduce(|mut a, b| {
a.combine(b);
a
}) {
return Err(e);
}
let (context_vis, mod_vis) = match (vis, module) {
(None, false) => (Visibility::Inherited, None),
(None, true) => (
syn::parse2::<Visibility>(quote! { pub(super) }).unwrap(),
Some(Visibility::Inherited),
),
(Some(vis), false) => (vis, None),
(Some(vis), true) => (vis.clone(), Some(vis)),
};
Ok(TypeAttr {
display,
context_vis,
mod_vis,
})
}
pub(crate) fn parse_variant_attr(attrs: &[Attribute]) -> syn::Result<VariantAttr> {
fn inner(attr: &Attribute, display: &mut Option<TokenStream>, errors: &mut Vec<syn::Error>) {
if !attr.path().is_ident("error2") {
return;
}
let nested = match attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
Ok(o) => o,
Err(e) => {
errors.push(e);
return;
}
};
for meta in nested {
let path = meta.path();
let Some(path_ident) = path.get_ident() else {
errors.push(syn::Error::new(path.span(), EXPECTED_IDENT));
continue;
};
if path_ident == "display" {
let list = match meta {
Meta::List(meta_list) => meta_list,
Meta::Path(_) | Meta::NameValue(_) => {
errors.push(syn::Error::new(meta.span(), DISPLAY_MUST_IN_META_LIST));
continue;
}
};
if !display.is_none() {
errors.push(syn::Error::new(
list.span(),
specified_multiple_times("display"),
));
continue;
}
*display = Some(list.tokens);
} else {
errors.push(syn::Error::new(
path_ident.span(),
unknown_single_attr(path_ident, "display"),
));
}
}
}
let mut display = None;
let mut errors = Vec::new();
attrs
.iter()
.for_each(|attr| inner(attr, &mut display, &mut errors));
if let Some(e) = errors.into_iter().reduce(|mut a, b| {
a.combine(b);
a
}) {
return Err(e);
}
Ok(VariantAttr { display })
}