summaryrefslogtreecommitdiffstats
path: root/servo/components/style_derive
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style_derive')
-rw-r--r--servo/components/style_derive/Cargo.toml18
-rw-r--r--servo/components/style_derive/animate.rs135
-rw-r--r--servo/components/style_derive/compute_squared_distance.rs125
-rw-r--r--servo/components/style_derive/lib.rs82
-rw-r--r--servo/components/style_derive/parse.rs323
-rw-r--r--servo/components/style_derive/specified_value_info.rs195
-rw-r--r--servo/components/style_derive/to_animated_value.rs35
-rw-r--r--servo/components/style_derive/to_animated_zero.rs65
-rw-r--r--servo/components/style_derive/to_computed_value.rs205
-rw-r--r--servo/components/style_derive/to_css.rs396
-rw-r--r--servo/components/style_derive/to_resolved_value.rs52
11 files changed, 1631 insertions, 0 deletions
diff --git a/servo/components/style_derive/Cargo.toml b/servo/components/style_derive/Cargo.toml
new file mode 100644
index 0000000000..35f36b04ef
--- /dev/null
+++ b/servo/components/style_derive/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "style_derive"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+path = "lib.rs"
+proc-macro = true
+
+[dependencies]
+darling = { version = "0.20", default-features = false }
+derive_common = { path = "../derive_common" }
+proc-macro2 = "1"
+quote = "1"
+syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] }
+synstructure = "0.13"
diff --git a/servo/components/style_derive/animate.rs b/servo/components/style_derive/animate.rs
new file mode 100644
index 0000000000..9549100ad0
--- /dev/null
+++ b/servo/components/style_derive/animate.rs
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use darling::util::PathList;
+use derive_common::cg;
+use proc_macro2::TokenStream;
+use quote::TokenStreamExt;
+use syn::{DeriveInput, WhereClause};
+use synstructure::{Structure, VariantInfo};
+
+pub fn derive(mut input: DeriveInput) -> TokenStream {
+ let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input);
+
+ let no_bound = animation_input_attrs.no_bound.unwrap_or_default();
+ let mut where_clause = input.generics.where_clause.take();
+ for param in input.generics.type_params() {
+ if !no_bound.iter().any(|name| name.is_ident(&param.ident)) {
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(#param: crate::values::animated::Animate),
+ );
+ }
+ }
+ let (mut match_body, needs_catchall_branch) = {
+ let s = Structure::new(&input);
+ let needs_catchall_branch = s.variants().len() > 1;
+ let match_body = s.variants().iter().fold(quote!(), |body, variant| {
+ let arm = derive_variant_arm(variant, &mut where_clause);
+ quote! { #body #arm }
+ });
+ (match_body, needs_catchall_branch)
+ };
+
+ input.generics.where_clause = where_clause;
+
+ if needs_catchall_branch {
+ // This ideally shouldn't be needed, but see
+ // https://github.com/rust-lang/rust/issues/68867
+ match_body.append_all(quote! { _ => unsafe { debug_unreachable!() } });
+ }
+
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ quote! {
+ impl #impl_generics crate::values::animated::Animate for #name #ty_generics #where_clause {
+ #[allow(unused_variables, unused_imports)]
+ #[inline]
+ fn animate(
+ &self,
+ other: &Self,
+ procedure: crate::values::animated::Procedure,
+ ) -> Result<Self, ()> {
+ if std::mem::discriminant(self) != std::mem::discriminant(other) {
+ return Err(());
+ }
+ match (self, other) {
+ #match_body
+ }
+ }
+ }
+ }
+}
+
+fn derive_variant_arm(
+ variant: &VariantInfo,
+ where_clause: &mut Option<WhereClause>,
+) -> TokenStream {
+ let variant_attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast());
+ let (this_pattern, this_info) = cg::ref_pattern(&variant, "this");
+ let (other_pattern, other_info) = cg::ref_pattern(&variant, "other");
+
+ if variant_attrs.error {
+ return quote! {
+ (&#this_pattern, &#other_pattern) => Err(()),
+ };
+ }
+
+ let (result_value, result_info) = cg::value(&variant, "result");
+ let mut computations = quote!();
+ let iter = result_info.iter().zip(this_info.iter().zip(&other_info));
+ computations.append_all(iter.map(|(result, (this, other))| {
+ let field_attrs = cg::parse_field_attrs::<AnimationFieldAttrs>(&result.ast());
+ if field_attrs.field_bound {
+ let ty = &this.ast().ty;
+ cg::add_predicate(
+ where_clause,
+ parse_quote!(#ty: crate::values::animated::Animate),
+ );
+ }
+ if field_attrs.constant {
+ quote! {
+ if #this != #other {
+ return Err(());
+ }
+ let #result = std::clone::Clone::clone(#this);
+ }
+ } else {
+ quote! {
+ let #result =
+ crate::values::animated::Animate::animate(#this, #other, procedure)?;
+ }
+ }
+ }));
+
+ quote! {
+ (&#this_pattern, &#other_pattern) => {
+ #computations
+ Ok(#result_value)
+ }
+ }
+}
+
+#[derive(Default, FromDeriveInput)]
+#[darling(attributes(animation), default)]
+pub struct AnimationInputAttrs {
+ pub no_bound: Option<PathList>,
+}
+
+#[derive(Default, FromVariant)]
+#[darling(attributes(animation), default)]
+pub struct AnimationVariantAttrs {
+ pub error: bool,
+ // Only here because of structs, where the struct definition acts as a
+ // variant itself.
+ pub no_bound: Option<PathList>,
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(animation), default)]
+pub struct AnimationFieldAttrs {
+ pub constant: bool,
+ pub field_bound: bool,
+}
diff --git a/servo/components/style_derive/compute_squared_distance.rs b/servo/components/style_derive/compute_squared_distance.rs
new file mode 100644
index 0000000000..022ab115ee
--- /dev/null
+++ b/servo/components/style_derive/compute_squared_distance.rs
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::animate::{AnimationFieldAttrs, AnimationInputAttrs, AnimationVariantAttrs};
+use derive_common::cg;
+use proc_macro2::TokenStream;
+use quote::TokenStreamExt;
+use syn::{DeriveInput, WhereClause};
+use synstructure;
+
+pub fn derive(mut input: DeriveInput) -> TokenStream {
+ let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input);
+ let no_bound = animation_input_attrs.no_bound.unwrap_or_default();
+ let mut where_clause = input.generics.where_clause.take();
+ for param in input.generics.type_params() {
+ if !no_bound.iter().any(|name| name.is_ident(&param.ident)) {
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(#param: crate::values::distance::ComputeSquaredDistance),
+ );
+ }
+ }
+
+ let (mut match_body, needs_catchall_branch) = {
+ let s = synstructure::Structure::new(&input);
+ let needs_catchall_branch = s.variants().len() > 1;
+
+ let match_body = s.variants().iter().fold(quote!(), |body, variant| {
+ let arm = derive_variant_arm(variant, &mut where_clause);
+ quote! { #body #arm }
+ });
+
+ (match_body, needs_catchall_branch)
+ };
+
+ input.generics.where_clause = where_clause;
+
+ if needs_catchall_branch {
+ // This ideally shouldn't be needed, but see:
+ // https://github.com/rust-lang/rust/issues/68867
+ match_body.append_all(quote! { _ => unsafe { debug_unreachable!() } });
+ }
+
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ quote! {
+ impl #impl_generics crate::values::distance::ComputeSquaredDistance for #name #ty_generics #where_clause {
+ #[allow(unused_variables, unused_imports)]
+ #[inline]
+ fn compute_squared_distance(
+ &self,
+ other: &Self,
+ ) -> Result<crate::values::distance::SquaredDistance, ()> {
+ if std::mem::discriminant(self) != std::mem::discriminant(other) {
+ return Err(());
+ }
+ match (self, other) {
+ #match_body
+ }
+ }
+ }
+ }
+}
+
+fn derive_variant_arm(
+ variant: &synstructure::VariantInfo,
+ mut where_clause: &mut Option<WhereClause>,
+) -> TokenStream {
+ let variant_attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast());
+ let (this_pattern, this_info) = cg::ref_pattern(&variant, "this");
+ let (other_pattern, other_info) = cg::ref_pattern(&variant, "other");
+
+ if variant_attrs.error {
+ return quote! {
+ (&#this_pattern, &#other_pattern) => Err(()),
+ };
+ }
+
+ let sum = if this_info.is_empty() {
+ quote! { crate::values::distance::SquaredDistance::from_sqrt(0.) }
+ } else {
+ let mut sum = quote!();
+ sum.append_separated(this_info.iter().zip(&other_info).map(|(this, other)| {
+ let field_attrs = cg::parse_field_attrs::<DistanceFieldAttrs>(&this.ast());
+ if field_attrs.field_bound {
+ let ty = &this.ast().ty;
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(#ty: crate::values::distance::ComputeSquaredDistance),
+ );
+ }
+
+ let animation_field_attrs =
+ cg::parse_field_attrs::<AnimationFieldAttrs>(&this.ast());
+
+ if animation_field_attrs.constant {
+ quote! {
+ {
+ if #this != #other {
+ return Err(());
+ }
+ crate::values::distance::SquaredDistance::from_sqrt(0.)
+ }
+ }
+ } else {
+ quote! {
+ crate::values::distance::ComputeSquaredDistance::compute_squared_distance(#this, #other)?
+ }
+ }
+ }), quote!(+));
+ sum
+ };
+
+ return quote! {
+ (&#this_pattern, &#other_pattern) => Ok(#sum),
+ };
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(distance), default)]
+struct DistanceFieldAttrs {
+ field_bound: bool,
+}
diff --git a/servo/components/style_derive/lib.rs b/servo/components/style_derive/lib.rs
new file mode 100644
index 0000000000..079db00c5a
--- /dev/null
+++ b/servo/components/style_derive/lib.rs
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#![recursion_limit = "128"]
+
+#[macro_use]
+extern crate darling;
+extern crate derive_common;
+extern crate proc_macro;
+extern crate proc_macro2;
+#[macro_use]
+extern crate quote;
+#[macro_use]
+extern crate syn;
+extern crate synstructure;
+
+use proc_macro::TokenStream;
+
+mod animate;
+mod compute_squared_distance;
+mod parse;
+mod specified_value_info;
+mod to_animated_value;
+mod to_animated_zero;
+mod to_computed_value;
+mod to_css;
+mod to_resolved_value;
+
+#[proc_macro_derive(Animate, attributes(animate, animation))]
+pub fn derive_animate(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ animate::derive(input).into()
+}
+
+#[proc_macro_derive(ComputeSquaredDistance, attributes(animation, distance))]
+pub fn derive_compute_squared_distance(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ compute_squared_distance::derive(input).into()
+}
+
+#[proc_macro_derive(ToAnimatedValue)]
+pub fn derive_to_animated_value(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ to_animated_value::derive(input).into()
+}
+
+#[proc_macro_derive(Parse, attributes(css, parse))]
+pub fn derive_parse(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ parse::derive(input).into()
+}
+
+#[proc_macro_derive(ToAnimatedZero, attributes(animation, zero))]
+pub fn derive_to_animated_zero(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ to_animated_zero::derive(input).into()
+}
+
+#[proc_macro_derive(ToComputedValue, attributes(compute))]
+pub fn derive_to_computed_value(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ to_computed_value::derive(input).into()
+}
+
+#[proc_macro_derive(ToResolvedValue, attributes(resolve))]
+pub fn derive_to_resolved_value(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ to_resolved_value::derive(input).into()
+}
+
+#[proc_macro_derive(ToCss, attributes(css))]
+pub fn derive_to_css(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ to_css::derive(input).into()
+}
+
+#[proc_macro_derive(SpecifiedValueInfo, attributes(css, parse, value_info))]
+pub fn derive_specified_value_info(stream: TokenStream) -> TokenStream {
+ let input = syn::parse(stream).unwrap();
+ specified_value_info::derive(input).into()
+}
diff --git a/servo/components/style_derive/parse.rs b/servo/components/style_derive/parse.rs
new file mode 100644
index 0000000000..b1a1213435
--- /dev/null
+++ b/servo/components/style_derive/parse.rs
@@ -0,0 +1,323 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::to_css::{CssBitflagAttrs, CssVariantAttrs};
+use derive_common::cg;
+use proc_macro2::{Span, TokenStream};
+use quote::TokenStreamExt;
+use syn::{self, DeriveInput, Ident, Path};
+use synstructure::{Structure, VariantInfo};
+
+#[derive(Default, FromVariant)]
+#[darling(attributes(parse), default)]
+pub struct ParseVariantAttrs {
+ pub aliases: Option<String>,
+ pub condition: Option<Path>,
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(parse), default)]
+pub struct ParseFieldAttrs {
+ field_bound: bool,
+}
+
+fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream {
+ let mut match_arms = TokenStream::new();
+ for (rust_name, css_name) in bitflags.single_flags() {
+ let rust_ident = Ident::new(&rust_name, Span::call_site());
+ match_arms.append_all(quote! {
+ #css_name if result.is_empty() => {
+ single_flag = true;
+ Self::#rust_ident
+ },
+ });
+ }
+
+ for (rust_name, css_name) in bitflags.mixed_flags() {
+ let rust_ident = Ident::new(&rust_name, Span::call_site());
+ match_arms.append_all(quote! {
+ #css_name => Self::#rust_ident,
+ });
+ }
+
+ let mut validate_condition = quote! { !result.is_empty() };
+ if let Some(ref function) = bitflags.validate_mixed {
+ validate_condition.append_all(quote! {
+ && #function(&mut result)
+ });
+ }
+
+ // NOTE(emilio): this loop has this weird structure because we run this code
+ // to parse stuff like text-decoration-line in the text-decoration
+ // shorthand, so we need to be a bit careful that we don't error if we don't
+ // consume the whole thing because we find an invalid identifier or other
+ // kind of token. Instead, we should leave it unconsumed.
+ quote! {
+ let mut result = Self::empty();
+ loop {
+ let mut single_flag = false;
+ let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| {
+ Ok(try_match_ident_ignore_ascii_case! { input,
+ #match_arms
+ })
+ });
+
+ let flag = match flag {
+ Ok(flag) => flag,
+ Err(..) => break,
+ };
+
+ if single_flag {
+ return Ok(flag);
+ }
+
+ if result.intersects(flag) {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+
+ result.insert(flag);
+ }
+ if #validate_condition {
+ Ok(result)
+ } else {
+ Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
+ }
+ }
+}
+
+fn parse_non_keyword_variant(
+ where_clause: &mut Option<syn::WhereClause>,
+ name: &syn::Ident,
+ variant: &VariantInfo,
+ variant_attrs: &CssVariantAttrs,
+ parse_attrs: &ParseVariantAttrs,
+ skip_try: bool,
+) -> TokenStream {
+ let bindings = variant.bindings();
+ assert!(parse_attrs.aliases.is_none());
+ assert!(variant_attrs.function.is_none());
+ assert!(variant_attrs.keyword.is_none());
+ assert_eq!(
+ bindings.len(),
+ 1,
+ "We only support deriving parse for simple variants"
+ );
+ let variant_name = &variant.ast().ident;
+ let binding_ast = &bindings[0].ast();
+ let ty = &binding_ast.ty;
+
+ if let Some(ref bitflags) = variant_attrs.bitflags {
+ assert!(skip_try, "Should be the only variant");
+ assert!(
+ parse_attrs.condition.is_none(),
+ "Should be the only variant"
+ );
+ assert!(where_clause.is_none(), "Generic bitflags?");
+ return parse_bitflags(bitflags);
+ }
+
+ let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast);
+ if field_attrs.field_bound {
+ cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse));
+ }
+
+ let mut parse = if skip_try {
+ quote! {
+ let v = <#ty as crate::parser::Parse>::parse(context, input)?;
+ return Ok(#name::#variant_name(v));
+ }
+ } else {
+ quote! {
+ if let Ok(v) = input.try(|i| <#ty as crate::parser::Parse>::parse(context, i)) {
+ return Ok(#name::#variant_name(v));
+ }
+ }
+ };
+
+ if let Some(ref condition) = parse_attrs.condition {
+ parse = quote! {
+ if #condition(context) {
+ #parse
+ }
+ };
+
+ if skip_try {
+ // We're the last variant and we can fail to parse due to the
+ // condition clause. If that happens, we need to return an error.
+ parse = quote! {
+ #parse
+ Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError))
+ };
+ }
+ }
+
+ parse
+}
+
+pub fn derive(mut input: DeriveInput) -> TokenStream {
+ let mut where_clause = input.generics.where_clause.take();
+ for param in input.generics.type_params() {
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(#param: crate::parser::Parse),
+ );
+ }
+
+ let name = &input.ident;
+ let s = Structure::new(&input);
+
+ let mut saw_condition = false;
+ let mut match_keywords = quote! {};
+ let mut non_keywords = vec![];
+
+ let mut effective_variants = 0;
+ for variant in s.variants().iter() {
+ let css_variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&variant.ast());
+ if css_variant_attrs.skip {
+ continue;
+ }
+ effective_variants += 1;
+
+ let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast());
+
+ saw_condition |= parse_attrs.condition.is_some();
+
+ if !variant.bindings().is_empty() {
+ non_keywords.push((variant, css_variant_attrs, parse_attrs));
+ continue;
+ }
+
+ let identifier = cg::to_css_identifier(
+ &css_variant_attrs
+ .keyword
+ .unwrap_or_else(|| variant.ast().ident.to_string()),
+ );
+ let ident = &variant.ast().ident;
+
+ let condition = match parse_attrs.condition {
+ Some(ref p) => quote! { if #p(context) },
+ None => quote! {},
+ };
+
+ match_keywords.extend(quote! {
+ #identifier #condition => Ok(#name::#ident),
+ });
+
+ let aliases = match parse_attrs.aliases {
+ Some(aliases) => aliases,
+ None => continue,
+ };
+
+ for alias in aliases.split(',') {
+ match_keywords.extend(quote! {
+ #alias #condition => Ok(#name::#ident),
+ });
+ }
+ }
+
+ let needs_context = saw_condition || !non_keywords.is_empty();
+
+ let context_ident = if needs_context {
+ quote! { context }
+ } else {
+ quote! { _ }
+ };
+
+ let has_keywords = non_keywords.len() != effective_variants;
+
+ let mut parse_non_keywords = quote! {};
+ for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() {
+ let skip_try = !has_keywords && i == non_keywords.len() - 1;
+ let parse_variant = parse_non_keyword_variant(
+ &mut where_clause,
+ name,
+ variant,
+ css_attrs,
+ parse_attrs,
+ skip_try,
+ );
+ parse_non_keywords.extend(parse_variant);
+ }
+
+ let parse_body = if needs_context {
+ let parse_keywords = if has_keywords {
+ quote! {
+ let location = input.current_source_location();
+ let ident = input.expect_ident()?;
+ match_ignore_ascii_case! { &ident,
+ #match_keywords
+ _ => Err(location.new_unexpected_token_error(
+ cssparser::Token::Ident(ident.clone())
+ ))
+ }
+ }
+ } else {
+ quote! {}
+ };
+
+ quote! {
+ #parse_non_keywords
+ #parse_keywords
+ }
+ } else {
+ quote! { Self::parse(input) }
+ };
+
+ let has_non_keywords = !non_keywords.is_empty();
+
+ input.generics.where_clause = where_clause;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ let parse_trait_impl = quote! {
+ impl #impl_generics crate::parser::Parse for #name #ty_generics #where_clause {
+ #[inline]
+ fn parse<'i, 't>(
+ #context_ident: &crate::parser::ParserContext,
+ input: &mut cssparser::Parser<'i, 't>,
+ ) -> Result<Self, style_traits::ParseError<'i>> {
+ #parse_body
+ }
+ }
+ };
+
+ if needs_context {
+ return parse_trait_impl;
+ }
+
+ assert!(!has_non_keywords);
+
+ // TODO(emilio): It'd be nice to get rid of these, but that makes the
+ // conversion harder...
+ let methods_impl = quote! {
+ impl #name {
+ /// Parse this keyword.
+ #[inline]
+ pub fn parse<'i, 't>(
+ input: &mut cssparser::Parser<'i, 't>,
+ ) -> Result<Self, style_traits::ParseError<'i>> {
+ let location = input.current_source_location();
+ let ident = input.expect_ident()?;
+ Self::from_ident(ident.as_ref()).map_err(|()| {
+ location.new_unexpected_token_error(
+ cssparser::Token::Ident(ident.clone())
+ )
+ })
+ }
+
+ /// Parse this keyword from a string slice.
+ #[inline]
+ pub fn from_ident(ident: &str) -> Result<Self, ()> {
+ match_ignore_ascii_case! { ident,
+ #match_keywords
+ _ => Err(()),
+ }
+ }
+ }
+ };
+
+ quote! {
+ #parse_trait_impl
+ #methods_impl
+ }
+}
diff --git a/servo/components/style_derive/specified_value_info.rs b/servo/components/style_derive/specified_value_info.rs
new file mode 100644
index 0000000000..9a07ab49a6
--- /dev/null
+++ b/servo/components/style_derive/specified_value_info.rs
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::parse::ParseVariantAttrs;
+use crate::to_css::{CssFieldAttrs, CssInputAttrs, CssVariantAttrs};
+use derive_common::cg;
+use proc_macro2::TokenStream;
+use quote::TokenStreamExt;
+use syn::{Data, DeriveInput, Fields, Ident, Type};
+
+pub fn derive(mut input: DeriveInput) -> TokenStream {
+ let css_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
+ let mut types = vec![];
+ let mut values = vec![];
+
+ let input_ident = &input.ident;
+ let input_name = || cg::to_css_identifier(&input_ident.to_string());
+ if let Some(function) = css_attrs.function {
+ values.push(function.explicit().unwrap_or_else(input_name));
+ // If the whole value is wrapped in a function, value types of
+ // its fields should not be propagated.
+ } else {
+ let mut where_clause = input.generics.where_clause.take();
+ for param in input.generics.type_params() {
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(#param: style_traits::SpecifiedValueInfo),
+ );
+ }
+ input.generics.where_clause = where_clause;
+
+ match input.data {
+ Data::Enum(ref e) => {
+ for v in e.variants.iter() {
+ let css_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&v);
+ let info_attrs = cg::parse_variant_attrs::<ValueInfoVariantAttrs>(&v);
+ let parse_attrs = cg::parse_variant_attrs::<ParseVariantAttrs>(&v);
+ if css_attrs.skip {
+ continue;
+ }
+ if let Some(aliases) = parse_attrs.aliases {
+ for alias in aliases.split(',') {
+ values.push(alias.to_string());
+ }
+ }
+ if let Some(other_values) = info_attrs.other_values {
+ for value in other_values.split(',') {
+ values.push(value.to_string());
+ }
+ }
+ let ident = &v.ident;
+ let variant_name = || cg::to_css_identifier(&ident.to_string());
+ if info_attrs.starts_with_keyword {
+ values.push(variant_name());
+ continue;
+ }
+ if let Some(keyword) = css_attrs.keyword {
+ values.push(keyword);
+ continue;
+ }
+ if let Some(function) = css_attrs.function {
+ values.push(function.explicit().unwrap_or_else(variant_name));
+ } else if !derive_struct_fields(&v.fields, &mut types, &mut values) {
+ values.push(variant_name());
+ }
+ }
+ },
+ Data::Struct(ref s) => {
+ if let Some(ref bitflags) = css_attrs.bitflags {
+ for (_rust_name, css_name) in bitflags.single_flags() {
+ values.push(css_name)
+ }
+ for (_rust_name, css_name) in bitflags.mixed_flags() {
+ values.push(css_name)
+ }
+ } else if !derive_struct_fields(&s.fields, &mut types, &mut values) {
+ values.push(input_name());
+ }
+ },
+ Data::Union(_) => unreachable!("union is not supported"),
+ }
+ }
+
+ let info_attrs = cg::parse_input_attrs::<ValueInfoInputAttrs>(&input);
+ if let Some(other_values) = info_attrs.other_values {
+ for value in other_values.split(',') {
+ values.push(value.to_string());
+ }
+ }
+
+ let mut types_value = quote!(0);
+ types_value.append_all(types.iter().map(|ty| {
+ quote! {
+ | <#ty as style_traits::SpecifiedValueInfo>::SUPPORTED_TYPES
+ }
+ }));
+
+ let mut nested_collects = quote!();
+ nested_collects.append_all(types.iter().map(|ty| {
+ quote! {
+ <#ty as style_traits::SpecifiedValueInfo>::collect_completion_keywords(_f);
+ }
+ }));
+
+ if let Some(ty) = info_attrs.ty {
+ types_value.append_all(quote! {
+ | style_traits::CssType::#ty
+ });
+ }
+
+ let append_values = if values.is_empty() {
+ quote!()
+ } else {
+ let mut value_list = quote!();
+ value_list.append_separated(values.iter(), quote! { , });
+ quote! { _f(&[#value_list]); }
+ };
+
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ quote! {
+ impl #impl_generics style_traits::SpecifiedValueInfo for #name #ty_generics
+ #where_clause
+ {
+ const SUPPORTED_TYPES: u8 = #types_value;
+
+ fn collect_completion_keywords(_f: &mut FnMut(&[&'static str])) {
+ #nested_collects
+ #append_values
+ }
+ }
+ }
+}
+
+/// Derive from the given fields. Return false if the fields is a Unit,
+/// true otherwise.
+fn derive_struct_fields<'a>(
+ fields: &'a Fields,
+ types: &mut Vec<&'a Type>,
+ values: &mut Vec<String>,
+) -> bool {
+ let fields = match *fields {
+ Fields::Unit => return false,
+ Fields::Named(ref fields) => fields.named.iter(),
+ Fields::Unnamed(ref fields) => fields.unnamed.iter(),
+ };
+ types.extend(fields.filter_map(|field| {
+ let info_attrs = cg::parse_field_attrs::<ValueInfoFieldAttrs>(field);
+ if let Some(other_values) = info_attrs.other_values {
+ for value in other_values.split(',') {
+ values.push(value.to_string());
+ }
+ }
+ let css_attrs = cg::parse_field_attrs::<CssFieldAttrs>(field);
+ if css_attrs.represents_keyword {
+ let ident = field
+ .ident
+ .as_ref()
+ .expect("only named field should use represents_keyword");
+ values.push(cg::to_css_identifier(&ident.to_string()).replace("_", "-"));
+ return None;
+ }
+ if let Some(if_empty) = css_attrs.if_empty {
+ values.push(if_empty);
+ }
+ if !css_attrs.skip {
+ Some(&field.ty)
+ } else {
+ None
+ }
+ }));
+ true
+}
+
+#[derive(Default, FromDeriveInput)]
+#[darling(attributes(value_info), default)]
+struct ValueInfoInputAttrs {
+ ty: Option<Ident>,
+ other_values: Option<String>,
+}
+
+#[derive(Default, FromVariant)]
+#[darling(attributes(value_info), default)]
+struct ValueInfoVariantAttrs {
+ starts_with_keyword: bool,
+ other_values: Option<String>,
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(value_info), default)]
+struct ValueInfoFieldAttrs {
+ other_values: Option<String>,
+}
diff --git a/servo/components/style_derive/to_animated_value.rs b/servo/components/style_derive/to_animated_value.rs
new file mode 100644
index 0000000000..45282f0c44
--- /dev/null
+++ b/servo/components/style_derive/to_animated_value.rs
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use proc_macro2::TokenStream;
+use syn::DeriveInput;
+use synstructure::BindStyle;
+use to_computed_value;
+
+pub fn derive(input: DeriveInput) -> TokenStream {
+ let trait_impl = |from_body, to_body| {
+ quote! {
+ #[inline]
+ fn from_animated_value(from: Self::AnimatedValue) -> Self {
+ #from_body
+ }
+
+ #[inline]
+ fn to_animated_value(self) -> Self::AnimatedValue {
+ #to_body
+ }
+ }
+ };
+
+ to_computed_value::derive_to_value(
+ input,
+ parse_quote!(crate::values::animated::ToAnimatedValue),
+ parse_quote!(AnimatedValue),
+ BindStyle::Move,
+ |_| Default::default(),
+ |binding| quote!(crate::values::animated::ToAnimatedValue::from_animated_value(#binding)),
+ |binding| quote!(crate::values::animated::ToAnimatedValue::to_animated_value(#binding)),
+ trait_impl,
+ )
+}
diff --git a/servo/components/style_derive/to_animated_zero.rs b/servo/components/style_derive/to_animated_zero.rs
new file mode 100644
index 0000000000..008e94cbcf
--- /dev/null
+++ b/servo/components/style_derive/to_animated_zero.rs
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::animate::{AnimationFieldAttrs, AnimationInputAttrs, AnimationVariantAttrs};
+use derive_common::cg;
+use proc_macro2::TokenStream;
+use quote::TokenStreamExt;
+use syn;
+use synstructure;
+
+pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
+ let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input);
+ let no_bound = animation_input_attrs.no_bound.unwrap_or_default();
+ let mut where_clause = input.generics.where_clause.take();
+ for param in input.generics.type_params() {
+ if !no_bound.iter().any(|name| name.is_ident(&param.ident)) {
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(#param: crate::values::animated::ToAnimatedZero),
+ );
+ }
+ }
+
+ let to_body = synstructure::Structure::new(&input).each_variant(|variant| {
+ let attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast());
+ if attrs.error {
+ return Some(quote! { Err(()) });
+ }
+ let (mapped, mapped_bindings) = cg::value(variant, "mapped");
+ let bindings_pairs = variant.bindings().iter().zip(mapped_bindings);
+ let mut computations = quote!();
+ computations.append_all(bindings_pairs.map(|(binding, mapped_binding)| {
+ let field_attrs = cg::parse_field_attrs::<AnimationFieldAttrs>(&binding.ast());
+ if field_attrs.constant {
+ quote! {
+ let #mapped_binding = std::clone::Clone::clone(#binding);
+ }
+ } else {
+ quote! {
+ let #mapped_binding =
+ crate::values::animated::ToAnimatedZero::to_animated_zero(#binding)?;
+ }
+ }
+ }));
+ computations.append_all(quote! { Ok(#mapped) });
+ Some(computations)
+ });
+ input.generics.where_clause = where_clause;
+
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ quote! {
+ impl #impl_generics crate::values::animated::ToAnimatedZero for #name #ty_generics #where_clause {
+ #[allow(unused_variables)]
+ #[inline]
+ fn to_animated_zero(&self) -> Result<Self, ()> {
+ match *self {
+ #to_body
+ }
+ }
+ }
+ }
+}
diff --git a/servo/components/style_derive/to_computed_value.rs b/servo/components/style_derive/to_computed_value.rs
new file mode 100644
index 0000000000..5e0f595c6b
--- /dev/null
+++ b/servo/components/style_derive/to_computed_value.rs
@@ -0,0 +1,205 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use derive_common::cg;
+use proc_macro2::TokenStream;
+use syn::{DeriveInput, Ident, Path};
+use synstructure::{BindStyle, BindingInfo};
+
+pub fn derive_to_value(
+ mut input: DeriveInput,
+ trait_path: Path,
+ output_type_name: Ident,
+ bind_style: BindStyle,
+ // Returns whether to apply the field bound for a given item.
+ mut binding_attrs: impl FnMut(&BindingInfo) -> ToValueAttrs,
+ // Returns a token stream of the form: trait_path::from_foo(#binding)
+ mut call_from: impl FnMut(&BindingInfo) -> TokenStream,
+ mut call_to: impl FnMut(&BindingInfo) -> TokenStream,
+ // Returns a tokenstream of the form:
+ // fn from_function_syntax(foobar) -> Baz {
+ // #first_arg
+ // }
+ //
+ // fn to_function_syntax(foobar) -> Baz {
+ // #second_arg
+ // }
+ mut trait_impl: impl FnMut(TokenStream, TokenStream) -> TokenStream,
+) -> TokenStream {
+ let name = &input.ident;
+
+ let mut where_clause = input.generics.where_clause.take();
+ cg::propagate_clauses_to_output_type(
+ &mut where_clause,
+ &input.generics,
+ &trait_path,
+ &output_type_name,
+ );
+
+ let moves = match bind_style {
+ BindStyle::Move | BindStyle::MoveMut => true,
+ BindStyle::Ref | BindStyle::RefMut => false,
+ };
+
+ let params = input.generics.type_params().collect::<Vec<_>>();
+ for param in &params {
+ cg::add_predicate(&mut where_clause, parse_quote!(#param: #trait_path));
+ }
+
+ let computed_value_type = cg::fmap_trait_output(&input, &trait_path, &output_type_name);
+
+ let mut add_field_bound = |binding: &BindingInfo| {
+ let ty = &binding.ast().ty;
+
+ let output_type = cg::map_type_params(
+ ty,
+ &params,
+ &computed_value_type,
+ &mut |ident| parse_quote!(<#ident as #trait_path>::#output_type_name),
+ );
+
+ cg::add_predicate(
+ &mut where_clause,
+ parse_quote!(
+ #ty: #trait_path<#output_type_name = #output_type>
+ ),
+ );
+ };
+
+ let (to_body, from_body) = if params.is_empty() {
+ let mut s = synstructure::Structure::new(&input);
+ s.variants_mut().iter_mut().for_each(|v| {
+ v.bind_with(|_| bind_style);
+ });
+
+ for variant in s.variants() {
+ for binding in variant.bindings() {
+ let attrs = binding_attrs(&binding);
+ assert!(
+ !attrs.field_bound,
+ "It is default on a non-generic implementation",
+ );
+ if !attrs.no_field_bound {
+ // Add field bounds to all bindings except the manually
+ // excluded. This ensures the correctness of the clone() /
+ // move based implementation.
+ add_field_bound(binding);
+ }
+ }
+ }
+
+ let to_body = if moves {
+ quote! { self }
+ } else {
+ quote! { std::clone::Clone::clone(self) }
+ };
+
+ let from_body = if moves {
+ quote! { from }
+ } else {
+ quote! { std::clone::Clone::clone(from) }
+ };
+
+ (to_body, from_body)
+ } else {
+ let to_body = cg::fmap_match(&input, bind_style, |binding| {
+ let attrs = binding_attrs(&binding);
+ assert!(
+ !attrs.no_field_bound,
+ "It doesn't make sense on a generic implementation"
+ );
+ if attrs.field_bound {
+ add_field_bound(&binding);
+ }
+ call_to(&binding)
+ });
+
+ let from_body = cg::fmap_match(&input, bind_style, |binding| call_from(&binding));
+
+ let self_ = if moves {
+ quote! { self }
+ } else {
+ quote! { *self }
+ };
+ let from_ = if moves {
+ quote! { from }
+ } else {
+ quote! { *from }
+ };
+
+ let to_body = quote! {
+ match #self_ {
+ #to_body
+ }
+ };
+
+ let from_body = quote! {
+ match #from_ {
+ #from_body
+ }
+ };
+
+ (to_body, from_body)
+ };
+
+ input.generics.where_clause = where_clause;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ let impl_ = trait_impl(from_body, to_body);
+
+ quote! {
+ impl #impl_generics #trait_path for #name #ty_generics #where_clause {
+ type #output_type_name = #computed_value_type;
+
+ #impl_
+ }
+ }
+}
+
+pub fn derive(input: DeriveInput) -> TokenStream {
+ let trait_impl = |from_body, to_body| {
+ quote! {
+ #[inline]
+ fn from_computed_value(from: &Self::ComputedValue) -> Self {
+ #from_body
+ }
+
+ #[allow(unused_variables)]
+ #[inline]
+ fn to_computed_value(&self, context: &crate::values::computed::Context) -> Self::ComputedValue {
+ #to_body
+ }
+ }
+ };
+
+ derive_to_value(
+ input,
+ parse_quote!(crate::values::computed::ToComputedValue),
+ parse_quote!(ComputedValue),
+ BindStyle::Ref,
+ |binding| {
+ let attrs = cg::parse_field_attrs::<ComputedValueAttrs>(&binding.ast());
+ ToValueAttrs {
+ field_bound: attrs.field_bound,
+ no_field_bound: attrs.no_field_bound,
+ }
+ },
+ |binding| quote!(crate::values::computed::ToComputedValue::from_computed_value(#binding)),
+ |binding| quote!(crate::values::computed::ToComputedValue::to_computed_value(#binding, context)),
+ trait_impl,
+ )
+}
+
+#[derive(Default)]
+pub struct ToValueAttrs {
+ pub field_bound: bool,
+ pub no_field_bound: bool,
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(compute), default)]
+struct ComputedValueAttrs {
+ field_bound: bool,
+ no_field_bound: bool,
+}
diff --git a/servo/components/style_derive/to_css.rs b/servo/components/style_derive/to_css.rs
new file mode 100644
index 0000000000..aa33536648
--- /dev/null
+++ b/servo/components/style_derive/to_css.rs
@@ -0,0 +1,396 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use darling::util::Override;
+use derive_common::cg;
+use proc_macro2::{Span, TokenStream};
+use quote::{ToTokens, TokenStreamExt};
+use syn::{self, Data, Ident, Path, WhereClause};
+use synstructure::{BindingInfo, Structure, VariantInfo};
+
+fn derive_bitflags(input: &syn::DeriveInput, bitflags: &CssBitflagAttrs) -> TokenStream {
+ let name = &input.ident;
+ let mut body = TokenStream::new();
+ for (rust_name, css_name) in bitflags.single_flags() {
+ let rust_ident = Ident::new(&rust_name, Span::call_site());
+ body.append_all(quote! {
+ if *self == Self::#rust_ident {
+ return dest.write_str(#css_name);
+ }
+ });
+ }
+
+ body.append_all(quote! {
+ let mut has_any = false;
+ });
+
+ if bitflags.overlapping_bits {
+ body.append_all(quote! {
+ let mut serialized = Self::empty();
+ });
+ }
+
+ for (rust_name, css_name) in bitflags.mixed_flags() {
+ let rust_ident = Ident::new(&rust_name, Span::call_site());
+ let serialize = quote! {
+ if has_any {
+ dest.write_char(' ')?;
+ }
+ has_any = true;
+ dest.write_str(#css_name)?;
+ };
+ if bitflags.overlapping_bits {
+ body.append_all(quote! {
+ if self.contains(Self::#rust_ident) && !serialized.intersects(Self::#rust_ident) {
+ #serialize
+ serialized.insert(Self::#rust_ident);
+ }
+ });
+ } else {
+ body.append_all(quote! {
+ if self.intersects(Self::#rust_ident) {
+ #serialize
+ }
+ });
+ }
+ }
+
+ body.append_all(quote! {
+ Ok(())
+ });
+
+ quote! {
+ impl style_traits::ToCss for #name {
+ #[allow(unused_variables)]
+ #[inline]
+ fn to_css<W>(
+ &self,
+ dest: &mut style_traits::CssWriter<W>,
+ ) -> std::fmt::Result
+ where
+ W: std::fmt::Write,
+ {
+ #body
+ }
+ }
+ }
+}
+
+pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
+ let mut where_clause = input.generics.where_clause.take();
+ for param in input.generics.type_params() {
+ cg::add_predicate(&mut where_clause, parse_quote!(#param: style_traits::ToCss));
+ }
+
+ let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
+ if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() {
+ assert!(
+ input_attrs.function.is_none(),
+ "#[css(function)] is not allowed on enums or bitflags"
+ );
+ assert!(
+ !input_attrs.comma,
+ "#[css(comma)] is not allowed on enums or bitflags"
+ );
+ }
+
+ if let Some(ref bitflags) = input_attrs.bitflags {
+ assert!(
+ !input_attrs.derive_debug,
+ "Bitflags can derive debug on their own"
+ );
+ assert!(where_clause.is_none(), "Generic bitflags?");
+ return derive_bitflags(&input, bitflags);
+ }
+
+ let match_body = {
+ let s = Structure::new(&input);
+ s.each_variant(|variant| derive_variant_arm(variant, &mut where_clause))
+ };
+ input.generics.where_clause = where_clause;
+
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ let mut impls = quote! {
+ impl #impl_generics style_traits::ToCss for #name #ty_generics #where_clause {
+ #[allow(unused_variables)]
+ #[inline]
+ fn to_css<W>(
+ &self,
+ dest: &mut style_traits::CssWriter<W>,
+ ) -> std::fmt::Result
+ where
+ W: std::fmt::Write,
+ {
+ match *self {
+ #match_body
+ }
+ }
+ }
+ };
+
+ if input_attrs.derive_debug {
+ impls.append_all(quote! {
+ impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ style_traits::ToCss::to_css(
+ self,
+ &mut style_traits::CssWriter::new(f),
+ )
+ }
+ }
+ });
+ }
+
+ impls
+}
+
+fn derive_variant_arm(variant: &VariantInfo, generics: &mut Option<WhereClause>) -> TokenStream {
+ let bindings = variant.bindings();
+ let identifier = cg::to_css_identifier(&variant.ast().ident.to_string());
+ let ast = variant.ast();
+ let variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&ast);
+ let separator = if variant_attrs.comma { ", " } else { " " };
+
+ if variant_attrs.skip {
+ return quote!(Ok(()));
+ }
+ if variant_attrs.dimension {
+ assert_eq!(bindings.len(), 1);
+ assert!(
+ variant_attrs.function.is_none() && variant_attrs.keyword.is_none(),
+ "That makes no sense"
+ );
+ }
+
+ let mut expr = if let Some(keyword) = variant_attrs.keyword {
+ assert!(bindings.is_empty());
+ quote! {
+ std::fmt::Write::write_str(dest, #keyword)
+ }
+ } else if !bindings.is_empty() {
+ derive_variant_fields_expr(bindings, generics, separator)
+ } else {
+ quote! {
+ std::fmt::Write::write_str(dest, #identifier)
+ }
+ };
+
+ if variant_attrs.dimension {
+ expr = quote! {
+ #expr?;
+ std::fmt::Write::write_str(dest, #identifier)
+ }
+ } else if let Some(function) = variant_attrs.function {
+ let mut identifier = function.explicit().map_or(identifier, |name| name);
+ identifier.push('(');
+ expr = quote! {
+ std::fmt::Write::write_str(dest, #identifier)?;
+ #expr?;
+ std::fmt::Write::write_str(dest, ")")
+ }
+ }
+ expr
+}
+
+fn derive_variant_fields_expr(
+ bindings: &[BindingInfo],
+ where_clause: &mut Option<WhereClause>,
+ separator: &str,
+) -> TokenStream {
+ let mut iter = bindings
+ .iter()
+ .filter_map(|binding| {
+ let attrs = cg::parse_field_attrs::<CssFieldAttrs>(&binding.ast());
+ if attrs.skip {
+ return None;
+ }
+ Some((binding, attrs))
+ })
+ .peekable();
+
+ let (first, attrs) = match iter.next() {
+ Some(pair) => pair,
+ None => return quote! { Ok(()) },
+ };
+ if attrs.field_bound {
+ let ty = &first.ast().ty;
+ // TODO(emilio): IntoIterator might not be enough for every type of
+ // iterable thing (like ArcSlice<> or what not). We might want to expose
+ // an `item = "T"` attribute to handle that in the future.
+ let predicate = if attrs.iterable {
+ parse_quote!(<#ty as IntoIterator>::Item: style_traits::ToCss)
+ } else {
+ parse_quote!(#ty: style_traits::ToCss)
+ };
+ cg::add_predicate(where_clause, predicate);
+ }
+ if !attrs.iterable && iter.peek().is_none() {
+ let mut expr = quote! { style_traits::ToCss::to_css(#first, dest) };
+ if let Some(condition) = attrs.skip_if {
+ expr = quote! {
+ if !#condition(#first) {
+ #expr
+ }
+ }
+ }
+
+ if let Some(condition) = attrs.contextual_skip_if {
+ expr = quote! {
+ if !#condition(#(#bindings), *) {
+ #expr
+ }
+ }
+ }
+ return expr;
+ }
+
+ let mut expr = derive_single_field_expr(first, attrs, where_clause, bindings);
+ for (binding, attrs) in iter {
+ derive_single_field_expr(binding, attrs, where_clause, bindings).to_tokens(&mut expr)
+ }
+
+ quote! {{
+ let mut writer = style_traits::values::SequenceWriter::new(dest, #separator);
+ #expr
+ Ok(())
+ }}
+}
+
+fn derive_single_field_expr(
+ field: &BindingInfo,
+ attrs: CssFieldAttrs,
+ where_clause: &mut Option<WhereClause>,
+ bindings: &[BindingInfo],
+) -> TokenStream {
+ let mut expr = if attrs.iterable {
+ if let Some(if_empty) = attrs.if_empty {
+ return quote! {
+ {
+ let mut iter = #field.iter().peekable();
+ if iter.peek().is_none() {
+ writer.raw_item(#if_empty)?;
+ } else {
+ for item in iter {
+ writer.item(&item)?;
+ }
+ }
+ }
+ };
+ }
+ quote! {
+ for item in #field.iter() {
+ writer.item(&item)?;
+ }
+ }
+ } else if attrs.represents_keyword {
+ let ident = field
+ .ast()
+ .ident
+ .as_ref()
+ .expect("Unnamed field with represents_keyword?");
+ let ident = cg::to_css_identifier(&ident.to_string()).replace("_", "-");
+ quote! {
+ if *#field {
+ writer.raw_item(#ident)?;
+ }
+ }
+ } else {
+ if attrs.field_bound {
+ let ty = &field.ast().ty;
+ cg::add_predicate(where_clause, parse_quote!(#ty: style_traits::ToCss));
+ }
+ quote! { writer.item(#field)?; }
+ };
+
+ if let Some(condition) = attrs.skip_if {
+ expr = quote! {
+ if !#condition(#field) {
+ #expr
+ }
+ }
+ }
+
+ if let Some(condition) = attrs.contextual_skip_if {
+ expr = quote! {
+ if !#condition(#(#bindings), *) {
+ #expr
+ }
+ }
+ }
+
+ expr
+}
+
+#[derive(Default, FromMeta)]
+#[darling(default)]
+pub struct CssBitflagAttrs {
+ /// Flags that can only go on their own, comma-separated.
+ pub single: Option<String>,
+ /// Flags that can go mixed with each other, comma-separated.
+ pub mixed: Option<String>,
+ /// Extra validation of the resulting mixed flags.
+ pub validate_mixed: Option<Path>,
+ /// Whether there are overlapping bits we need to take care of when
+ /// serializing.
+ pub overlapping_bits: bool,
+}
+
+impl CssBitflagAttrs {
+ /// Returns a vector of (rust_name, css_name) of a given flag list.
+ fn names(s: &Option<String>) -> Vec<(String, String)> {
+ let s = match s {
+ Some(s) => s,
+ None => return vec![],
+ };
+ s.split(',')
+ .map(|css_name| (cg::to_scream_case(css_name), css_name.to_owned()))
+ .collect()
+ }
+
+ pub fn single_flags(&self) -> Vec<(String, String)> {
+ Self::names(&self.single)
+ }
+
+ pub fn mixed_flags(&self) -> Vec<(String, String)> {
+ Self::names(&self.mixed)
+ }
+}
+
+#[derive(Default, FromDeriveInput)]
+#[darling(attributes(css), default)]
+pub struct CssInputAttrs {
+ pub derive_debug: bool,
+ // Here because structs variants are also their whole type definition.
+ pub function: Option<Override<String>>,
+ // Here because structs variants are also their whole type definition.
+ pub comma: bool,
+ pub bitflags: Option<CssBitflagAttrs>,
+}
+
+#[derive(Default, FromVariant)]
+#[darling(attributes(css), default)]
+pub struct CssVariantAttrs {
+ pub function: Option<Override<String>>,
+ // Here because structs variants are also their whole type definition.
+ pub derive_debug: bool,
+ pub comma: bool,
+ pub bitflags: Option<CssBitflagAttrs>,
+ pub dimension: bool,
+ pub keyword: Option<String>,
+ pub skip: bool,
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(css), default)]
+pub struct CssFieldAttrs {
+ pub if_empty: Option<String>,
+ pub field_bound: bool,
+ pub iterable: bool,
+ pub skip: bool,
+ pub represents_keyword: bool,
+ pub contextual_skip_if: Option<Path>,
+ pub skip_if: Option<Path>,
+}
diff --git a/servo/components/style_derive/to_resolved_value.rs b/servo/components/style_derive/to_resolved_value.rs
new file mode 100644
index 0000000000..e049f91152
--- /dev/null
+++ b/servo/components/style_derive/to_resolved_value.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use derive_common::cg;
+use proc_macro2::TokenStream;
+use syn::DeriveInput;
+use synstructure::BindStyle;
+use to_computed_value;
+
+pub fn derive(input: DeriveInput) -> TokenStream {
+ let trait_impl = |from_body, to_body| {
+ quote! {
+ #[inline]
+ fn from_resolved_value(from: Self::ResolvedValue) -> Self {
+ #from_body
+ }
+
+ #[inline]
+ fn to_resolved_value(
+ self,
+ context: &crate::values::resolved::Context,
+ ) -> Self::ResolvedValue {
+ #to_body
+ }
+ }
+ };
+
+ to_computed_value::derive_to_value(
+ input,
+ parse_quote!(crate::values::resolved::ToResolvedValue),
+ parse_quote!(ResolvedValue),
+ BindStyle::Move,
+ |binding| {
+ let attrs = cg::parse_field_attrs::<ResolvedValueAttrs>(&binding.ast());
+ to_computed_value::ToValueAttrs {
+ field_bound: attrs.field_bound,
+ no_field_bound: attrs.no_field_bound,
+ }
+ },
+ |binding| quote!(crate::values::resolved::ToResolvedValue::from_resolved_value(#binding)),
+ |binding| quote!(crate::values::resolved::ToResolvedValue::to_resolved_value(#binding, context)),
+ trait_impl,
+ )
+}
+
+#[derive(Default, FromField)]
+#[darling(attributes(resolve), default)]
+struct ResolvedValueAttrs {
+ field_bound: bool,
+ no_field_bound: bool,
+}