-
Notifications
You must be signed in to change notification settings - Fork 189
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use macros to derive implementations of Parse and ToCss for many enums
- Loading branch information
1 parent
fa6c015
commit 76e31cf
Showing
39 changed files
with
1,100 additions
and
1,763 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,293 +1,20 @@ | ||
use std::collections::HashSet; | ||
use proc_macro::TokenStream; | ||
|
||
use proc_macro::{self, TokenStream}; | ||
use proc_macro2::{Span, TokenStream as TokenStream2}; | ||
use quote::quote; | ||
use syn::{ | ||
parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, | ||
GenericParam, Generics, Ident, Member, Token, Type, Visibility, | ||
}; | ||
mod parse; | ||
mod to_css; | ||
mod visit; | ||
|
||
#[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))] | ||
pub fn derive_visit_children(input: TokenStream) -> TokenStream { | ||
let DeriveInput { | ||
ident, | ||
data, | ||
generics, | ||
attrs, | ||
.. | ||
} = parse_macro_input!(input); | ||
|
||
let options: Vec<VisitOptions> = attrs | ||
.iter() | ||
.filter_map(|attr| { | ||
if attr.path.is_ident("visit") { | ||
let opts: VisitOptions = attr.parse_args().unwrap(); | ||
Some(opts) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect(); | ||
|
||
let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) { | ||
let types: VisitTypes = attr.parse_args().unwrap(); | ||
let types = types.types; | ||
Some(quote! { crate::visit_types!(#(#types)|*) }) | ||
} else { | ||
None | ||
}; | ||
|
||
if options.is_empty() { | ||
derive(&ident, &data, &generics, None, visit_types) | ||
} else { | ||
options | ||
.into_iter() | ||
.map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone())) | ||
.collect() | ||
} | ||
} | ||
|
||
fn derive( | ||
ident: &Ident, | ||
data: &Data, | ||
generics: &Generics, | ||
options: Option<VisitOptions>, | ||
visit_types: Option<TokenStream2>, | ||
) -> TokenStream { | ||
let mut impl_generics = generics.clone(); | ||
let mut type_defs = quote! {}; | ||
let generics = if let Some(VisitOptions { | ||
generic: Some(generic), .. | ||
}) = &options | ||
{ | ||
let mappings = generics | ||
.type_params() | ||
.zip(generic.type_params()) | ||
.map(|(a, b)| quote! { type #a = #b; }); | ||
type_defs = quote! { #(#mappings)* }; | ||
impl_generics.params.clear(); | ||
generic | ||
} else { | ||
&generics | ||
}; | ||
|
||
if impl_generics.lifetimes().next().is_none() { | ||
impl_generics.params.insert(0, parse_quote! { 'i }) | ||
} | ||
|
||
let lifetime = impl_generics.lifetimes().next().unwrap().clone(); | ||
let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R"); | ||
let v = quote! { __V }; | ||
let t = if let Some(t) = t { | ||
GenericParam::Type(t.ident.clone().into()) | ||
} else { | ||
let t: GenericParam = parse_quote! { __T }; | ||
impl_generics | ||
.params | ||
.push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> }); | ||
t | ||
}; | ||
|
||
impl_generics | ||
.params | ||
.push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> }); | ||
|
||
for ty in generics.type_params() { | ||
let name = &ty.ident; | ||
impl_generics.make_where_clause().predicates.push(parse_quote! { | ||
#name: Visit<#lifetime, #t, #v> | ||
}) | ||
} | ||
|
||
let mut seen_types = HashSet::new(); | ||
let mut child_types = Vec::new(); | ||
let mut visit = Vec::new(); | ||
match data { | ||
Data::Struct(s) => { | ||
for ( | ||
index, | ||
Field { | ||
vis, ty, ident, attrs, .. | ||
}, | ||
) in s.fields.iter().enumerate() | ||
{ | ||
if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) { | ||
continue; | ||
} | ||
|
||
if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) { | ||
continue; | ||
} | ||
|
||
if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) { | ||
seen_types.insert(ty.clone()); | ||
child_types.push(quote! { | ||
<#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() | ||
}); | ||
} | ||
|
||
let name = ident | ||
.as_ref() | ||
.map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone())); | ||
visit.push(quote! { self.#name.visit(visitor)?; }) | ||
} | ||
} | ||
Data::Enum(DataEnum { variants, .. }) => { | ||
let variants = variants | ||
.iter() | ||
.map(|variant| { | ||
let name = &variant.ident; | ||
let mut field_names = Vec::new(); | ||
let mut visit_fields = Vec::new(); | ||
for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() { | ||
let name = ident.as_ref().map_or_else( | ||
|| Ident::new(&format!("_{}", index), Span::call_site()), | ||
|ident| ident.clone(), | ||
); | ||
field_names.push(name.clone()); | ||
|
||
if matches!(ty, Type::Reference(_)) { | ||
continue; | ||
} | ||
|
||
if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs) | ||
{ | ||
seen_types.insert(ty.clone()); | ||
child_types.push(quote! { | ||
<#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits() | ||
}); | ||
} | ||
|
||
visit_fields.push(quote! { #name.visit(visitor)?; }) | ||
} | ||
|
||
match variant.fields { | ||
Fields::Unnamed(_) => { | ||
quote! { | ||
Self::#name(#(#field_names),*) => { | ||
#(#visit_fields)* | ||
} | ||
} | ||
} | ||
Fields::Named(_) => { | ||
quote! { | ||
Self::#name { #(#field_names),* } => { | ||
#(#visit_fields)* | ||
} | ||
} | ||
} | ||
Fields::Unit => quote! {}, | ||
} | ||
}) | ||
.collect::<proc_macro2::TokenStream>(); | ||
|
||
visit.push(quote! { | ||
match self { | ||
#variants | ||
_ => {} | ||
} | ||
}) | ||
} | ||
_ => {} | ||
} | ||
|
||
if visit_types.is_none() && child_types.is_empty() { | ||
child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() }); | ||
} | ||
|
||
let (_, ty_generics, _) = generics.split_for_impl(); | ||
let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); | ||
|
||
let self_visit = if let Some(VisitOptions { | ||
visit: Some(visit), | ||
kind: Some(kind), | ||
.. | ||
}) = &options | ||
{ | ||
child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() }); | ||
|
||
quote! { | ||
fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { | ||
if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) { | ||
visitor.#visit(self) | ||
} else { | ||
self.visit_children(visitor) | ||
} | ||
} | ||
} | ||
} else { | ||
quote! {} | ||
}; | ||
|
||
let child_types = visit_types.unwrap_or_else(|| { | ||
quote! { | ||
{ | ||
#type_defs | ||
crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*) | ||
} | ||
} | ||
}); | ||
|
||
let output = quote! { | ||
impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause { | ||
const CHILD_TYPES: crate::visitor::VisitTypes = #child_types; | ||
|
||
#self_visit | ||
|
||
fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> { | ||
if !<Self as Visit<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) { | ||
return Ok(()) | ||
} | ||
|
||
#(#visit)* | ||
|
||
Ok(()) | ||
} | ||
} | ||
}; | ||
|
||
output.into() | ||
} | ||
|
||
fn skip_type(attrs: &Vec<Attribute>) -> bool { | ||
attrs.iter().any(|attr| attr.path.is_ident("skip_type")) | ||
} | ||
|
||
struct VisitOptions { | ||
visit: Option<Ident>, | ||
kind: Option<Ident>, | ||
generic: Option<Generics>, | ||
} | ||
|
||
impl Parse for VisitOptions { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
let (visit, kind, comma) = if input.peek(Ident) { | ||
let visit: Ident = input.parse()?; | ||
let _: Token![,] = input.parse()?; | ||
let kind: Ident = input.parse()?; | ||
let comma: Result<Token![,], _> = input.parse(); | ||
(Some(visit), Some(kind), comma.is_ok()) | ||
} else { | ||
(None, None, true) | ||
}; | ||
let generic: Option<Generics> = if comma { Some(input.parse()?) } else { None }; | ||
Ok(Self { visit, kind, generic }) | ||
} | ||
visit::derive_visit_children(input) | ||
} | ||
|
||
struct VisitTypes { | ||
types: Vec<Ident>, | ||
#[proc_macro_derive(Parse, attributes(css))] | ||
pub fn derive_parse(input: TokenStream) -> TokenStream { | ||
parse::derive_parse(input) | ||
} | ||
|
||
impl Parse for VisitTypes { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
let first: Ident = input.parse()?; | ||
let mut types = vec![first]; | ||
while input.parse::<Token![|]>().is_ok() { | ||
let id: Ident = input.parse()?; | ||
types.push(id); | ||
} | ||
Ok(Self { types }) | ||
} | ||
#[proc_macro_derive(ToCss, attributes(css))] | ||
pub fn derive_to_css(input: TokenStream) -> TokenStream { | ||
to_css::derive_to_css(input) | ||
} |
Oops, something went wrong.