stylo_derive/
specified_value_info.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use crate::cg;
6use crate::parse::ParseVariantAttrs;
7use crate::to_css::{CssFieldAttrs, CssInputAttrs, CssVariantAttrs};
8use proc_macro2::TokenStream;
9use quote::TokenStreamExt;
10use syn::{Data, DeriveInput, Fields, Ident, Type};
11
12pub fn derive(mut input: DeriveInput) -> TokenStream {
13    let css_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
14    let mut types = vec![];
15    let mut values = vec![];
16
17    let input_ident = &input.ident;
18    let input_name = || cg::to_css_identifier(&input_ident.to_string());
19    if let Some(function) = css_attrs.function {
20        values.push(function.explicit().unwrap_or_else(input_name));
21    // If the whole value is wrapped in a function, value types of
22    // its fields should not be propagated.
23    } else {
24        let mut where_clause = input.generics.where_clause.take();
25        for param in input.generics.type_params() {
26            cg::add_predicate(
27                &mut where_clause,
28                parse_quote!(#param: style_traits::SpecifiedValueInfo),
29            );
30        }
31        input.generics.where_clause = where_clause;
32
33        match input.data {
34            Data::Enum(ref e) => {
35                for v in e.variants.iter() {
36                    let css_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&v);
37                    let info_attrs = cg::parse_variant_attrs::<ValueInfoVariantAttrs>(&v);
38                    let parse_attrs = cg::parse_variant_attrs::<ParseVariantAttrs>(&v);
39                    if css_attrs.skip {
40                        continue;
41                    }
42                    if let Some(aliases) = parse_attrs.aliases {
43                        for alias in aliases.split(',') {
44                            values.push(alias.to_string());
45                        }
46                    }
47                    if let Some(other_values) = info_attrs.other_values {
48                        for value in other_values.split(',') {
49                            values.push(value.to_string());
50                        }
51                    }
52                    let ident = &v.ident;
53                    let variant_name = || cg::to_css_identifier(&ident.to_string());
54                    if info_attrs.starts_with_keyword {
55                        values.push(variant_name());
56                        continue;
57                    }
58                    if let Some(keyword) = css_attrs.keyword {
59                        values.push(keyword);
60                        continue;
61                    }
62                    if let Some(function) = css_attrs.function {
63                        values.push(function.explicit().unwrap_or_else(variant_name));
64                    } else if !derive_struct_fields(&v.fields, &mut types, &mut values) {
65                        values.push(variant_name());
66                    }
67                }
68            },
69            Data::Struct(ref s) => {
70                if let Some(ref bitflags) = css_attrs.bitflags {
71                    for (_rust_name, css_name) in bitflags.single_flags() {
72                        values.push(css_name)
73                    }
74                    for (_rust_name, css_name) in bitflags.mixed_flags() {
75                        values.push(css_name)
76                    }
77                } else if !derive_struct_fields(&s.fields, &mut types, &mut values) {
78                    values.push(input_name());
79                }
80            },
81            Data::Union(_) => unreachable!("union is not supported"),
82        }
83    }
84
85    let info_attrs = cg::parse_input_attrs::<ValueInfoInputAttrs>(&input);
86    if let Some(other_values) = info_attrs.other_values {
87        for value in other_values.split(',') {
88            values.push(value.to_string());
89        }
90    }
91
92    let mut types_value = quote!(0);
93    types_value.append_all(types.iter().map(|ty| {
94        quote! {
95            | <#ty as style_traits::SpecifiedValueInfo>::SUPPORTED_TYPES
96        }
97    }));
98
99    let mut nested_collects = quote!();
100    nested_collects.append_all(types.iter().map(|ty| {
101        quote! {
102            <#ty as style_traits::SpecifiedValueInfo>::collect_completion_keywords(_f);
103        }
104    }));
105
106    if let Some(ty) = info_attrs.ty {
107        types_value.append_all(quote! {
108            | style_traits::CssType::#ty
109        });
110    }
111
112    let append_values = if values.is_empty() {
113        quote!()
114    } else {
115        let mut value_list = quote!();
116        value_list.append_separated(values.iter(), quote! { , });
117        quote! { _f(&[#value_list]); }
118    };
119
120    let name = &input.ident;
121    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
122
123    quote! {
124        impl #impl_generics style_traits::SpecifiedValueInfo for #name #ty_generics
125        #where_clause
126        {
127            const SUPPORTED_TYPES: u8 = #types_value;
128
129            fn collect_completion_keywords(_f: &mut dyn FnMut(&[&'static str])) {
130                #nested_collects
131                #append_values
132            }
133        }
134    }
135}
136
137/// Derive from the given fields. Return false if the fields is a Unit,
138/// true otherwise.
139fn derive_struct_fields<'a>(
140    fields: &'a Fields,
141    types: &mut Vec<&'a Type>,
142    values: &mut Vec<String>,
143) -> bool {
144    let fields = match *fields {
145        Fields::Unit => return false,
146        Fields::Named(ref fields) => fields.named.iter(),
147        Fields::Unnamed(ref fields) => fields.unnamed.iter(),
148    };
149    types.extend(fields.filter_map(|field| {
150        let info_attrs = cg::parse_field_attrs::<ValueInfoFieldAttrs>(field);
151        if let Some(other_values) = info_attrs.other_values {
152            for value in other_values.split(',') {
153                values.push(value.to_string());
154            }
155        }
156        let css_attrs = cg::parse_field_attrs::<CssFieldAttrs>(field);
157        if css_attrs.represents_keyword {
158            let ident = field
159                .ident
160                .as_ref()
161                .expect("only named field should use represents_keyword");
162            values.push(cg::to_css_identifier(&ident.to_string()).replace("_", "-"));
163            return None;
164        }
165        if let Some(if_empty) = css_attrs.if_empty {
166            values.push(if_empty);
167        }
168        if !css_attrs.skip {
169            Some(&field.ty)
170        } else {
171            None
172        }
173    }));
174    true
175}
176
177#[derive(Default, FromDeriveInput)]
178#[darling(attributes(value_info), default)]
179struct ValueInfoInputAttrs {
180    ty: Option<Ident>,
181    other_values: Option<String>,
182}
183
184#[derive(Default, FromVariant)]
185#[darling(attributes(value_info), default)]
186struct ValueInfoVariantAttrs {
187    starts_with_keyword: bool,
188    other_values: Option<String>,
189}
190
191#[derive(Default, FromField)]
192#[darling(attributes(value_info), default)]
193struct ValueInfoFieldAttrs {
194    other_values: Option<String>,
195}