to_css.rs (12077B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 use crate::cg; 6 use darling::util::Override; 7 use proc_macro2::{Span, TokenStream}; 8 use quote::{ToTokens, TokenStreamExt}; 9 use syn::{self, Data, Ident, Path, WhereClause}; 10 use synstructure::{BindingInfo, Structure, VariantInfo}; 11 12 fn derive_bitflags(input: &syn::DeriveInput, bitflags: &CssBitflagAttrs) -> TokenStream { 13 let name = &input.ident; 14 let mut body = TokenStream::new(); 15 for (rust_name, css_name) in bitflags.single_flags() { 16 let rust_ident = Ident::new(&rust_name, Span::call_site()); 17 body.append_all(quote! { 18 if *self == Self::#rust_ident { 19 return dest.write_str(#css_name); 20 } 21 }); 22 } 23 24 body.append_all(quote! { 25 let mut has_any = false; 26 }); 27 28 if bitflags.overlapping_bits { 29 body.append_all(quote! { 30 let mut serialized = Self::empty(); 31 }); 32 } 33 34 for (rust_name, css_name) in bitflags.mixed_flags() { 35 let rust_ident = Ident::new(&rust_name, Span::call_site()); 36 let serialize = quote! { 37 if has_any { 38 dest.write_char(' ')?; 39 } 40 has_any = true; 41 dest.write_str(#css_name)?; 42 }; 43 if bitflags.overlapping_bits { 44 body.append_all(quote! { 45 if self.contains(Self::#rust_ident) && !serialized.intersects(Self::#rust_ident) { 46 #serialize 47 serialized.insert(Self::#rust_ident); 48 } 49 }); 50 } else { 51 body.append_all(quote! { 52 if self.intersects(Self::#rust_ident) { 53 #serialize 54 } 55 }); 56 } 57 } 58 59 body.append_all(quote! { 60 Ok(()) 61 }); 62 63 quote! { 64 impl style_traits::ToCss for #name { 65 #[allow(unused_variables)] 66 #[inline] 67 fn to_css<W>( 68 &self, 69 dest: &mut style_traits::CssWriter<W>, 70 ) -> std::fmt::Result 71 where 72 W: std::fmt::Write, 73 { 74 #body 75 } 76 } 77 } 78 } 79 80 pub fn derive(mut input: syn::DeriveInput) -> TokenStream { 81 let mut where_clause = input.generics.where_clause.take(); 82 for param in input.generics.type_params() { 83 cg::add_predicate(&mut where_clause, parse_quote!(#param: style_traits::ToCss)); 84 } 85 86 let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input); 87 if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() { 88 assert!( 89 input_attrs.function.is_none(), 90 "#[css(function)] is not allowed on enums or bitflags" 91 ); 92 assert!( 93 !input_attrs.comma, 94 "#[css(comma)] is not allowed on enums or bitflags" 95 ); 96 } 97 98 if let Some(ref bitflags) = input_attrs.bitflags { 99 assert!( 100 !input_attrs.derive_debug, 101 "Bitflags can derive debug on their own" 102 ); 103 assert!(where_clause.is_none(), "Generic bitflags?"); 104 return derive_bitflags(&input, bitflags); 105 } 106 107 let match_body = { 108 let s = Structure::new(&input); 109 s.each_variant(|variant| derive_variant_arm(variant, &mut where_clause)) 110 }; 111 input.generics.where_clause = where_clause; 112 113 let name = &input.ident; 114 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 115 116 let mut impls = quote! { 117 impl #impl_generics style_traits::ToCss for #name #ty_generics #where_clause { 118 #[allow(unused_variables)] 119 #[inline] 120 fn to_css<W>( 121 &self, 122 dest: &mut style_traits::CssWriter<W>, 123 ) -> std::fmt::Result 124 where 125 W: std::fmt::Write, 126 { 127 match *self { 128 #match_body 129 } 130 } 131 } 132 }; 133 134 if input_attrs.derive_debug { 135 impls.append_all(quote! { 136 impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause { 137 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 138 style_traits::ToCss::to_css( 139 self, 140 &mut style_traits::CssWriter::new(f), 141 ) 142 } 143 } 144 }); 145 } 146 147 impls 148 } 149 150 fn derive_variant_arm(variant: &VariantInfo, generics: &mut Option<WhereClause>) -> TokenStream { 151 let bindings = variant.bindings(); 152 let identifier = cg::to_css_identifier(&variant.ast().ident.to_string()); 153 let ast = variant.ast(); 154 let variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&ast); 155 let separator = if variant_attrs.comma { ", " } else { " " }; 156 157 if variant_attrs.skip { 158 return quote!(Ok(())); 159 } 160 if variant_attrs.dimension { 161 assert_eq!(bindings.len(), 1); 162 assert!( 163 variant_attrs.function.is_none() && variant_attrs.keyword.is_none(), 164 "That makes no sense" 165 ); 166 } 167 168 let mut expr = if let Some(keyword) = variant_attrs.keyword { 169 assert!(bindings.is_empty()); 170 quote! { 171 std::fmt::Write::write_str(dest, #keyword) 172 } 173 } else if !bindings.is_empty() { 174 derive_variant_fields_expr(bindings, generics, separator) 175 } else { 176 quote! { 177 std::fmt::Write::write_str(dest, #identifier) 178 } 179 }; 180 181 if variant_attrs.dimension { 182 expr = quote! { 183 #expr?; 184 std::fmt::Write::write_str(dest, #identifier) 185 } 186 } else if let Some(function) = variant_attrs.function { 187 let mut identifier = function.explicit().map_or(identifier, |name| name); 188 identifier.push('('); 189 expr = quote! { 190 std::fmt::Write::write_str(dest, #identifier)?; 191 #expr?; 192 std::fmt::Write::write_str(dest, ")") 193 } 194 } 195 expr 196 } 197 198 fn derive_variant_fields_expr( 199 bindings: &[BindingInfo], 200 where_clause: &mut Option<WhereClause>, 201 separator: &str, 202 ) -> TokenStream { 203 let mut iter = bindings 204 .iter() 205 .filter_map(|binding| { 206 let attrs = cg::parse_field_attrs::<CssFieldAttrs>(&binding.ast()); 207 if attrs.skip { 208 return None; 209 } 210 Some((binding, attrs)) 211 }) 212 .peekable(); 213 214 let (first, attrs) = match iter.next() { 215 Some(pair) => pair, 216 None => return quote! { Ok(()) }, 217 }; 218 if attrs.field_bound { 219 let ty = &first.ast().ty; 220 // TODO(emilio): IntoIterator might not be enough for every type of 221 // iterable thing (like ArcSlice<> or what not). We might want to expose 222 // an `item = "T"` attribute to handle that in the future. 223 let predicate = if attrs.iterable { 224 parse_quote!(<#ty as IntoIterator>::Item: style_traits::ToCss) 225 } else { 226 parse_quote!(#ty: style_traits::ToCss) 227 }; 228 cg::add_predicate(where_clause, predicate); 229 } 230 if !attrs.iterable && iter.peek().is_none() { 231 let mut expr = quote! { style_traits::ToCss::to_css(#first, dest) }; 232 if let Some(condition) = attrs.skip_if { 233 expr = quote! { 234 if !#condition(#first) { 235 #expr 236 } 237 } 238 } 239 240 if let Some(condition) = attrs.contextual_skip_if { 241 expr = quote! { 242 if !#condition(#(#bindings), *) { 243 #expr 244 } 245 } 246 } 247 return expr; 248 } 249 250 let mut expr = derive_single_field_expr(first, attrs, where_clause, bindings); 251 for (binding, attrs) in iter { 252 derive_single_field_expr(binding, attrs, where_clause, bindings).to_tokens(&mut expr) 253 } 254 255 quote! {{ 256 let mut writer = style_traits::values::SequenceWriter::new(dest, #separator); 257 #expr 258 Ok(()) 259 }} 260 } 261 262 fn derive_single_field_expr( 263 field: &BindingInfo, 264 attrs: CssFieldAttrs, 265 where_clause: &mut Option<WhereClause>, 266 bindings: &[BindingInfo], 267 ) -> TokenStream { 268 let mut expr = if attrs.iterable { 269 if let Some(if_empty) = attrs.if_empty { 270 return quote! { 271 { 272 let mut iter = #field.iter().peekable(); 273 if iter.peek().is_none() { 274 writer.raw_item(#if_empty)?; 275 } else { 276 for item in iter { 277 writer.item(&item)?; 278 } 279 } 280 } 281 }; 282 } 283 quote! { 284 for item in #field.iter() { 285 writer.item(&item)?; 286 } 287 } 288 } else if attrs.represents_keyword { 289 let ident = field 290 .ast() 291 .ident 292 .as_ref() 293 .expect("Unnamed field with represents_keyword?"); 294 let ident = cg::to_css_identifier(&ident.to_string()).replace("_", "-"); 295 quote! { 296 if *#field { 297 writer.raw_item(#ident)?; 298 } 299 } 300 } else { 301 if attrs.field_bound { 302 let ty = &field.ast().ty; 303 cg::add_predicate(where_clause, parse_quote!(#ty: style_traits::ToCss)); 304 } 305 quote! { writer.item(#field)?; } 306 }; 307 308 if let Some(condition) = attrs.skip_if { 309 expr = quote! { 310 if !#condition(#field) { 311 #expr 312 } 313 } 314 } 315 316 if let Some(condition) = attrs.contextual_skip_if { 317 expr = quote! { 318 if !#condition(#(#bindings), *) { 319 #expr 320 } 321 } 322 } 323 324 expr 325 } 326 327 #[derive(Default, FromMeta)] 328 #[darling(default)] 329 pub struct CssBitflagAttrs { 330 /// Flags that can only go on their own, comma-separated. 331 pub single: Option<String>, 332 /// Flags that can go mixed with each other, comma-separated. 333 pub mixed: Option<String>, 334 /// Extra validation of the resulting mixed flags. 335 pub validate_mixed: Option<Path>, 336 /// Whether there are overlapping bits we need to take care of when 337 /// serializing. 338 pub overlapping_bits: bool, 339 } 340 341 impl CssBitflagAttrs { 342 /// Returns a vector of (rust_name, css_name) of a given flag list. 343 fn names(s: &Option<String>) -> Vec<(String, String)> { 344 let s = match s { 345 Some(s) => s, 346 None => return vec![], 347 }; 348 s.split(',') 349 .map(|css_name| (cg::to_scream_case(css_name), css_name.to_owned())) 350 .collect() 351 } 352 353 pub fn single_flags(&self) -> Vec<(String, String)> { 354 Self::names(&self.single) 355 } 356 357 pub fn mixed_flags(&self) -> Vec<(String, String)> { 358 Self::names(&self.mixed) 359 } 360 } 361 362 #[derive(Default, FromDeriveInput)] 363 #[darling(attributes(css), default)] 364 pub struct CssInputAttrs { 365 pub derive_debug: bool, 366 // Here because structs variants are also their whole type definition. 367 pub function: Option<Override<String>>, 368 // Here because structs variants are also their whole type definition. 369 pub comma: bool, 370 pub bitflags: Option<CssBitflagAttrs>, 371 } 372 373 #[derive(Default, FromVariant)] 374 #[darling(attributes(css), default)] 375 pub struct CssVariantAttrs { 376 pub function: Option<Override<String>>, 377 // Here because structs variants are also their whole type definition. 378 pub derive_debug: bool, 379 pub comma: bool, 380 pub bitflags: Option<CssBitflagAttrs>, 381 pub dimension: bool, 382 pub keyword: Option<String>, 383 pub skip: bool, 384 } 385 386 #[derive(Default, FromField)] 387 #[darling(attributes(css), default)] 388 pub struct CssFieldAttrs { 389 pub if_empty: Option<String>, 390 pub field_bound: bool, 391 pub iterable: bool, 392 pub skip: bool, 393 pub represents_keyword: bool, 394 pub contextual_skip_if: Option<Path>, 395 pub skip_if: Option<Path>, 396 }