Skip to content

Commit 76e31cf

Browse files
committed
Use macros to derive implementations of Parse and ToCss for many enums
1 parent fa6c015 commit 76e31cf

39 files changed

+1100
-1763
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"]
4343
nodejs = ["dep:serde"]
4444
serde = ["dep:serde", "smallvec/serde", "cssparser/serde", "parcel_selectors/serde", "into_owned"]
4545
sourcemap = ["parcel_sourcemap"]
46-
visitor = ["lightningcss-derive"]
46+
visitor = []
4747
into_owned = ["static-self", "static-self/smallvec", "parcel_selectors/into_owned"]
4848
substitute_variables = ["visitor", "into_owned"]
4949

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

derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ proc-macro = true
1414
syn = { version = "1.0", features = ["extra-traits"] }
1515
quote = "1.0"
1616
proc-macro2 = "1.0"
17+
convert_case = "0.6.0"

derive/src/lib.rs

Lines changed: 11 additions & 284 deletions
Original file line numberDiff line numberDiff line change
@@ -1,293 +1,20 @@
1-
use std::collections::HashSet;
1+
use proc_macro::TokenStream;
22

3-
use proc_macro::{self, TokenStream};
4-
use proc_macro2::{Span, TokenStream as TokenStream2};
5-
use quote::quote;
6-
use syn::{
7-
parse::Parse, parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields,
8-
GenericParam, Generics, Ident, Member, Token, Type, Visibility,
9-
};
3+
mod parse;
4+
mod to_css;
5+
mod visit;
106

117
#[proc_macro_derive(Visit, attributes(visit, skip_visit, skip_type, visit_types))]
128
pub fn derive_visit_children(input: TokenStream) -> TokenStream {
13-
let DeriveInput {
14-
ident,
15-
data,
16-
generics,
17-
attrs,
18-
..
19-
} = parse_macro_input!(input);
20-
21-
let options: Vec<VisitOptions> = attrs
22-
.iter()
23-
.filter_map(|attr| {
24-
if attr.path.is_ident("visit") {
25-
let opts: VisitOptions = attr.parse_args().unwrap();
26-
Some(opts)
27-
} else {
28-
None
29-
}
30-
})
31-
.collect();
32-
33-
let visit_types = if let Some(attr) = attrs.iter().find(|attr| attr.path.is_ident("visit_types")) {
34-
let types: VisitTypes = attr.parse_args().unwrap();
35-
let types = types.types;
36-
Some(quote! { crate::visit_types!(#(#types)|*) })
37-
} else {
38-
None
39-
};
40-
41-
if options.is_empty() {
42-
derive(&ident, &data, &generics, None, visit_types)
43-
} else {
44-
options
45-
.into_iter()
46-
.map(|options| derive(&ident, &data, &generics, Some(options), visit_types.clone()))
47-
.collect()
48-
}
49-
}
50-
51-
fn derive(
52-
ident: &Ident,
53-
data: &Data,
54-
generics: &Generics,
55-
options: Option<VisitOptions>,
56-
visit_types: Option<TokenStream2>,
57-
) -> TokenStream {
58-
let mut impl_generics = generics.clone();
59-
let mut type_defs = quote! {};
60-
let generics = if let Some(VisitOptions {
61-
generic: Some(generic), ..
62-
}) = &options
63-
{
64-
let mappings = generics
65-
.type_params()
66-
.zip(generic.type_params())
67-
.map(|(a, b)| quote! { type #a = #b; });
68-
type_defs = quote! { #(#mappings)* };
69-
impl_generics.params.clear();
70-
generic
71-
} else {
72-
&generics
73-
};
74-
75-
if impl_generics.lifetimes().next().is_none() {
76-
impl_generics.params.insert(0, parse_quote! { 'i })
77-
}
78-
79-
let lifetime = impl_generics.lifetimes().next().unwrap().clone();
80-
let t = impl_generics.type_params().find(|g| &g.ident.to_string() == "R");
81-
let v = quote! { __V };
82-
let t = if let Some(t) = t {
83-
GenericParam::Type(t.ident.clone().into())
84-
} else {
85-
let t: GenericParam = parse_quote! { __T };
86-
impl_generics
87-
.params
88-
.push(parse_quote! { #t: crate::visitor::Visit<#lifetime, __T, #v> });
89-
t
90-
};
91-
92-
impl_generics
93-
.params
94-
.push(parse_quote! { #v: ?Sized + crate::visitor::Visitor<#lifetime, #t> });
95-
96-
for ty in generics.type_params() {
97-
let name = &ty.ident;
98-
impl_generics.make_where_clause().predicates.push(parse_quote! {
99-
#name: Visit<#lifetime, #t, #v>
100-
})
101-
}
102-
103-
let mut seen_types = HashSet::new();
104-
let mut child_types = Vec::new();
105-
let mut visit = Vec::new();
106-
match data {
107-
Data::Struct(s) => {
108-
for (
109-
index,
110-
Field {
111-
vis, ty, ident, attrs, ..
112-
},
113-
) in s.fields.iter().enumerate()
114-
{
115-
if attrs.iter().any(|attr| attr.path.is_ident("skip_visit")) {
116-
continue;
117-
}
118-
119-
if matches!(ty, Type::Reference(_)) || !matches!(vis, Visibility::Public(..)) {
120-
continue;
121-
}
122-
123-
if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) {
124-
seen_types.insert(ty.clone());
125-
child_types.push(quote! {
126-
<#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
127-
});
128-
}
129-
130-
let name = ident
131-
.as_ref()
132-
.map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));
133-
visit.push(quote! { self.#name.visit(visitor)?; })
134-
}
135-
}
136-
Data::Enum(DataEnum { variants, .. }) => {
137-
let variants = variants
138-
.iter()
139-
.map(|variant| {
140-
let name = &variant.ident;
141-
let mut field_names = Vec::new();
142-
let mut visit_fields = Vec::new();
143-
for (index, Field { ty, ident, attrs, .. }) in variant.fields.iter().enumerate() {
144-
let name = ident.as_ref().map_or_else(
145-
|| Ident::new(&format!("_{}", index), Span::call_site()),
146-
|ident| ident.clone(),
147-
);
148-
field_names.push(name.clone());
149-
150-
if matches!(ty, Type::Reference(_)) {
151-
continue;
152-
}
153-
154-
if visit_types.is_none() && !seen_types.contains(ty) && !skip_type(attrs) && !skip_type(&variant.attrs)
155-
{
156-
seen_types.insert(ty.clone());
157-
child_types.push(quote! {
158-
<#ty as Visit<#lifetime, #t, #v>>::CHILD_TYPES.bits()
159-
});
160-
}
161-
162-
visit_fields.push(quote! { #name.visit(visitor)?; })
163-
}
164-
165-
match variant.fields {
166-
Fields::Unnamed(_) => {
167-
quote! {
168-
Self::#name(#(#field_names),*) => {
169-
#(#visit_fields)*
170-
}
171-
}
172-
}
173-
Fields::Named(_) => {
174-
quote! {
175-
Self::#name { #(#field_names),* } => {
176-
#(#visit_fields)*
177-
}
178-
}
179-
}
180-
Fields::Unit => quote! {},
181-
}
182-
})
183-
.collect::<proc_macro2::TokenStream>();
184-
185-
visit.push(quote! {
186-
match self {
187-
#variants
188-
_ => {}
189-
}
190-
})
191-
}
192-
_ => {}
193-
}
194-
195-
if visit_types.is_none() && child_types.is_empty() {
196-
child_types.push(quote! { crate::visitor::VisitTypes::empty().bits() });
197-
}
198-
199-
let (_, ty_generics, _) = generics.split_for_impl();
200-
let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
201-
202-
let self_visit = if let Some(VisitOptions {
203-
visit: Some(visit),
204-
kind: Some(kind),
205-
..
206-
}) = &options
207-
{
208-
child_types.push(quote! { crate::visitor::VisitTypes::#kind.bits() });
209-
210-
quote! {
211-
fn visit(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
212-
if visitor.visit_types().contains(crate::visitor::VisitTypes::#kind) {
213-
visitor.#visit(self)
214-
} else {
215-
self.visit_children(visitor)
216-
}
217-
}
218-
}
219-
} else {
220-
quote! {}
221-
};
222-
223-
let child_types = visit_types.unwrap_or_else(|| {
224-
quote! {
225-
{
226-
#type_defs
227-
crate::visitor::VisitTypes::from_bits_retain(#(#child_types)|*)
228-
}
229-
}
230-
});
231-
232-
let output = quote! {
233-
impl #impl_generics Visit<#lifetime, #t, #v> for #ident #ty_generics #where_clause {
234-
const CHILD_TYPES: crate::visitor::VisitTypes = #child_types;
235-
236-
#self_visit
237-
238-
fn visit_children(&mut self, visitor: &mut #v) -> Result<(), #v::Error> {
239-
if !<Self as Visit<#lifetime, #t, #v>>::CHILD_TYPES.intersects(visitor.visit_types()) {
240-
return Ok(())
241-
}
242-
243-
#(#visit)*
244-
245-
Ok(())
246-
}
247-
}
248-
};
249-
250-
output.into()
251-
}
252-
253-
fn skip_type(attrs: &Vec<Attribute>) -> bool {
254-
attrs.iter().any(|attr| attr.path.is_ident("skip_type"))
255-
}
256-
257-
struct VisitOptions {
258-
visit: Option<Ident>,
259-
kind: Option<Ident>,
260-
generic: Option<Generics>,
261-
}
262-
263-
impl Parse for VisitOptions {
264-
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
265-
let (visit, kind, comma) = if input.peek(Ident) {
266-
let visit: Ident = input.parse()?;
267-
let _: Token![,] = input.parse()?;
268-
let kind: Ident = input.parse()?;
269-
let comma: Result<Token![,], _> = input.parse();
270-
(Some(visit), Some(kind), comma.is_ok())
271-
} else {
272-
(None, None, true)
273-
};
274-
let generic: Option<Generics> = if comma { Some(input.parse()?) } else { None };
275-
Ok(Self { visit, kind, generic })
276-
}
9+
visit::derive_visit_children(input)
27710
}
27811

279-
struct VisitTypes {
280-
types: Vec<Ident>,
12+
#[proc_macro_derive(Parse, attributes(css))]
13+
pub fn derive_parse(input: TokenStream) -> TokenStream {
14+
parse::derive_parse(input)
28115
}
28216

283-
impl Parse for VisitTypes {
284-
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
285-
let first: Ident = input.parse()?;
286-
let mut types = vec![first];
287-
while input.parse::<Token![|]>().is_ok() {
288-
let id: Ident = input.parse()?;
289-
types.push(id);
290-
}
291-
Ok(Self { types })
292-
}
17+
#[proc_macro_derive(ToCss, attributes(css))]
18+
pub fn derive_to_css(input: TokenStream) -> TokenStream {
19+
to_css::derive_to_css(input)
29320
}

0 commit comments

Comments
 (0)