Skip to content

Commit

Permalink
Use macros to derive implementations of Parse and ToCss for many enums
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Jun 3, 2024
1 parent fa6c015 commit 76e31cf
Show file tree
Hide file tree
Showing 39 changed files with 1,100 additions and 1,763 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"]
nodejs = ["dep:serde"]
serde = ["dep:serde", "smallvec/serde", "cssparser/serde", "parcel_selectors/serde", "into_owned"]
sourcemap = ["parcel_sourcemap"]
visitor = ["lightningcss-derive"]
visitor = []
into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"]
substitute_variables = ["visitor", "into_owned"]

Expand All @@ -69,7 +69,7 @@ browserslist-rs = { version = "0.15.0", optional = true }
rayon = { version = "1.5.1", optional = true }
dashmap = { version = "5.0.0", optional = true }
serde_json = { version = "1.0.78", optional = true }
lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive", optional = true }
lightningcss-derive = { version = "=1.0.0-alpha.42", path = "./derive" }
schemars = { version = "0.8.19", features = ["smallvec"], optional = true }
static-self = { version = "0.1.0", path = "static-self", optional = true }

Expand Down
1 change: 1 addition & 0 deletions derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ proc-macro = true
syn = { version = "1.0", features = ["extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
convert_case = "0.6.0"
295 changes: 11 additions & 284 deletions derive/src/lib.rs
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)
}
Loading

0 comments on commit 76e31cf

Please sign in to comment.