#![allow(clippy::eval_order_dependence)]
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{
bracketed, parse_macro_input, token, Error, Fields, ItemEnum, ItemStruct, Result, Token, Type,
};
mod kw {
syn::custom_keyword!(Option);
}
struct IdentArray {
_bracket_token: token::Bracket,
idents: Punctuated<Ident, Token![,]>,
}
impl Parse for IdentArray {
fn parse(input: ParseStream) -> Result<Self> {
let content;
Ok(IdentArray {
_bracket_token: bracketed!(content in input),
idents: content.parse_terminated(Ident::parse)?,
})
}
}
enum Attribute {
NoDerives {
ident: Ident,
_comma: token::Comma,
key_idents: IdentArray,
},
Derives {
ident: Ident,
_comma_1: token::Comma,
key_idents: IdentArray,
_comma_2: token::Comma,
derive_idents: IdentArray,
},
}
impl Parse for Attribute {
fn parse(input: ParseStream) -> Result<Self> {
let ident: Ident = input.parse()?;
let comma: token::Comma = input.parse()?;
let idents: IdentArray = input.parse()?;
let lookahead = input.lookahead1();
if lookahead.peek(token::Comma) {
Ok(Attribute::Derives {
ident,
_comma_1: comma,
key_idents: idents,
_comma_2: input.parse()?,
derive_idents: input.parse()?,
})
} else {
Ok(Attribute::NoDerives {
ident,
_comma: comma,
key_idents: idents,
})
}
}
}
struct TypeOption {
_option_token: kw::Option,
_lt_token: Token![<],
ty: Type,
_rt_token: Token![>],
}
impl Parse for TypeOption {
fn parse(input: ParseStream) -> Result<Self> {
Ok(TypeOption {
_option_token: input.parse()?,
_lt_token: input.parse()?,
ty: input.parse()?,
_rt_token: input.parse()?,
})
}
}
#[proc_macro_attribute]
pub fn partial(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr_ident = parse_macro_input!(attr as Ident);
let input = parse_macro_input!(input as ItemStruct);
let tokens = match &input {
ItemStruct {
vis,
generics,
fields: Fields::Named(fields),
..
} => {
let field_vis = fields.named.iter().map(|f| &f.vis);
let field_ident = fields.named.iter().map(|f| &f.ident);
let field_type = fields.named.iter().map(|f| &f.ty);
quote! {
#input
#vis struct #attr_ident #generics {
#(#field_vis #field_ident: Option<#field_type>),*
}
}
}
ItemStruct {
vis,
generics,
fields: Fields::Unnamed(fields),
..
} => {
let field_vis = fields.unnamed.iter().map(|f| &f.vis);
let field_type = fields.unnamed.iter().map(|f| &f.ty);
quote! {
#input
#vis struct #attr_ident #generics (#(#field_vis Option<#field_type>),*);
}
}
_ => Error::new_spanned(&input, "Must define on a struct with fields").to_compile_error(),
};
tokens.into()
}
#[proc_macro_attribute]
pub fn required(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr_ident = parse_macro_input!(attr as Ident);
let input = parse_macro_input!(input as ItemStruct);
let tokens = match &input {
ItemStruct {
vis,
generics,
fields: Fields::Named(fields),
..
} => {
let field_vis = fields.named.iter().map(|f| &f.vis);
let field_ident = fields.named.iter().map(|f| &f.ident);
let field_type = fields.named.iter().map(|f| match &f.ty {
Type::Path(type_path) => {
let parse_option = syn::parse::<TypeOption>(TokenStream::from(
type_path.path.segments.to_token_stream(),
));
match parse_option {
Ok(option) => option.ty,
_ => f.ty.clone(),
}
}
_ => f.ty.clone(),
});
quote! {
#input
#vis struct #attr_ident #generics {
#(#field_vis #field_ident: #field_type),*
}
}
}
ItemStruct {
vis,
generics,
fields: Fields::Unnamed(fields),
..
} => {
let field_vis = fields.unnamed.iter().map(|f| &f.vis);
let field_type = fields.unnamed.iter().map(|f| &f.ty);
quote! {
#input
#vis struct #attr_ident #generics (#(#field_vis #field_type),*);
}
}
_ => Error::new_spanned(&input, "Must define on a struct with fields").to_compile_error(),
};
tokens.into()
}
#[proc_macro_attribute]
pub fn pick(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr_input = parse_macro_input!(attr as Attribute);
let input = parse_macro_input!(input as ItemStruct);
let tokens = match attr_input {
Attribute::NoDerives {
ident, key_idents, ..
} => match &input {
ItemStruct {
vis,
generics,
fields: Fields::Named(fields),
..
} => {
let fields = fields.named.iter().filter(|f| {
key_idents
.idents
.iter()
.any(|k| *k == *f.ident.as_ref().unwrap())
});
quote! {
#input
#vis struct #ident #generics {
#(#fields),*
}
}
}
_ => Error::new_spanned(&input, "Must define on a struct with named field")
.to_compile_error(),
},
Attribute::Derives {
ident,
key_idents,
derive_idents,
..
} => match &input {
ItemStruct {
vis,
generics,
fields: Fields::Named(fields),
..
} => {
let fields = fields.named.iter().filter(|f| {
key_idents
.idents
.iter()
.any(|k| *k == *f.ident.as_ref().unwrap())
});
let derive_idents = derive_idents.idents;
quote! {
#input
#[derive(#derive_idents)]
#vis struct #ident #generics {
#(#fields),*
}
}
}
_ => Error::new_spanned(&input, "Must define on a struct with named field")
.to_compile_error(),
},
};
tokens.into()
}
#[proc_macro_attribute]
pub fn omit(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr_input = parse_macro_input!(attr as Attribute);
let input = parse_macro_input!(input as ItemStruct);
let tokens = match attr_input {
Attribute::NoDerives {
ident, key_idents, ..
} => match &input {
ItemStruct {
vis,
generics,
fields: Fields::Named(fields),
..
} => {
let fields = fields.named.iter().filter(|f| {
key_idents
.idents
.iter()
.all(|k| *k != *f.ident.as_ref().unwrap())
});
quote! {
#input
#vis struct #ident #generics {
#(#fields),*
}
}
}
_ => Error::new_spanned(&input, "Must define on a struct with named field")
.to_compile_error(),
},
Attribute::Derives {
ident,
key_idents,
derive_idents,
..
} => match &input {
ItemStruct {
vis,
generics,
fields: Fields::Named(fields),
..
} => {
let fields = fields.named.iter().filter(|f| {
key_idents
.idents
.iter()
.all(|k| *k != *f.ident.as_ref().unwrap())
});
let derive_idents = derive_idents.idents;
quote! {
#input
#[derive(#derive_idents)]
#vis struct #ident #generics {
#(#fields),*
}
}
}
_ => Error::new_spanned(&input, "Must define on a struct with named field")
.to_compile_error(),
},
};
tokens.into()
}
#[proc_macro_attribute]
pub fn exclude(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr_input = parse_macro_input!(attr as Attribute);
let input = parse_macro_input!(input as ItemEnum);
let tokens = match attr_input {
Attribute::NoDerives {
ident, key_idents, ..
} => {
let ItemEnum {
vis,
generics,
variants,
..
} = &input;
let variants = variants
.iter()
.filter(|v| key_idents.idents.iter().all(|k| *k != v.ident));
quote! {
#input
#vis enum #ident #generics {
#(#variants),*
}
}
}
Attribute::Derives {
ident,
key_idents,
derive_idents,
..
} => {
let ItemEnum {
vis,
generics,
variants,
..
} = &input;
let variants = variants
.iter()
.filter(|v| key_idents.idents.iter().all(|k| *k != v.ident));
let derive_idents = derive_idents.idents;
quote! {
#input
#[derive(#derive_idents)]
#vis enum #ident #generics {
#(#variants),*
}
}
}
};
tokens.into()
}
#[proc_macro_attribute]
pub fn extract(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr_input = parse_macro_input!(attr as Attribute);
let input = parse_macro_input!(input as ItemEnum);
let tokens = match attr_input {
Attribute::NoDerives {
ident, key_idents, ..
} => {
let ItemEnum {
vis,
generics,
variants,
..
} = &input;
let variants = variants
.iter()
.filter(|v| key_idents.idents.iter().any(|k| *k == v.ident));
quote! {
#input
#vis enum #ident #generics {
#(#variants),*
}
}
}
Attribute::Derives {
ident,
key_idents,
derive_idents,
..
} => {
let ItemEnum {
vis,
generics,
variants,
..
} = &input;
let variants = variants
.iter()
.filter(|v| key_idents.idents.iter().any(|k| *k == v.ident));
let derive_idents = derive_idents.idents;
quote! {
#input
#[derive(#derive_idents)]
#vis enum #ident #generics {
#(#variants),*
}
}
}
};
tokens.into()
}