diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /servo/components/style_derive | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style_derive')
-rw-r--r-- | servo/components/style_derive/Cargo.toml | 18 | ||||
-rw-r--r-- | servo/components/style_derive/animate.rs | 135 | ||||
-rw-r--r-- | servo/components/style_derive/compute_squared_distance.rs | 125 | ||||
-rw-r--r-- | servo/components/style_derive/lib.rs | 82 | ||||
-rw-r--r-- | servo/components/style_derive/parse.rs | 323 | ||||
-rw-r--r-- | servo/components/style_derive/specified_value_info.rs | 195 | ||||
-rw-r--r-- | servo/components/style_derive/to_animated_value.rs | 35 | ||||
-rw-r--r-- | servo/components/style_derive/to_animated_zero.rs | 65 | ||||
-rw-r--r-- | servo/components/style_derive/to_computed_value.rs | 205 | ||||
-rw-r--r-- | servo/components/style_derive/to_css.rs | 396 | ||||
-rw-r--r-- | servo/components/style_derive/to_resolved_value.rs | 52 |
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..67d7bcad3c --- /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.14", default-features = false } +derive_common = { path = "../derive_common" } +proc-macro2 = "1" +quote = "1" +syn = { version = "1", default-features = false, features = ["clone-impls", "derive", "parsing"] } +synstructure = "0.12" 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(¶m.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(¶m.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..e29f2bc416 --- /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())); + 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(¶m.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 ¶ms { + 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, + ¶ms, + &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, +} |