diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 91147b8de..a3e2a0079 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -1,14 +1,13 @@ use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, Ident, Result, Token}; use crate::util::{ determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr, - parse_flag, parse_string, parse_string_array, quote_option, validate_attrs, - BlockWithReturn, + parse_flag, parse_string, parse_string_array, validate_attrs, BlockWithReturn, }; /// Expand the `#[elem]` macro. @@ -26,7 +25,6 @@ struct Elem { docs: String, vis: syn::Visibility, ident: Ident, - enum_ident: Ident, capabilities: Vec, fields: Vec, } @@ -43,73 +41,60 @@ impl Elem { fn cannot(&self, name: &str) -> bool { !self.can(name) } +} - /// All fields. - /// - /// This includes: - /// - Fields that are not external and therefore present in the struct. - /// - Fields that are ghost fields. +impl Elem { + /// All fields that are not just external. fn real_fields(&self) -> impl Iterator + Clone { self.fields.iter().filter(|field| !field.external) } - /// Fields that are present in the struct. - /// - /// This includes: - /// - Fields that are not external and therefore present in the struct. - /// - Fields that are not ghost fields. - fn present_fields(&self) -> impl Iterator + Clone { + /// Fields that are present in the generated struct. + fn struct_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| !field.ghost) } - /// Fields that are inherent to the element. - fn inherent_fields(&self) -> impl Iterator + Clone { - self.real_fields().filter(|field| field.inherent()) - } - - /// Fields that can be set with style rules. - /// - /// The reason why fields that are `parse` and internal are allowed - /// is because it's a pattern used a lot for parsing data from the - /// input and then storing it in a field. - /// - /// This includes: - /// - Fields that are not synthesized. - /// - Fields that are not inherent and therefore present at all times. - /// - Fields that are not internal. - fn settable_fields(&self) -> impl Iterator + Clone { - self.real_fields().filter(|field| { - !field.synthesized - && field.settable() - && (!field.internal || field.parse.is_some()) - }) - } - - /// Fields that are visible to the user. - /// - /// This includes: - /// - Fields that are not internal. - fn visible_fields(&self) -> impl Iterator + Clone { - self.real_fields().filter(|field| !field.internal) - } - /// Fields that are relevant for equality. /// - /// This includes: - /// - Fields that are not synthesized (guarantees equality before and after synthesis). + /// Synthesized fields are excluded to ensure equality before and after + /// synthesis. fn eq_fields(&self) -> impl Iterator + Clone { - self.present_fields().filter(|field| !field.synthesized) + self.struct_fields().filter(|field| !field.synthesized) + } + + /// Fields that show up in the documentation. + fn doc_fields(&self) -> impl Iterator + Clone { + self.fields + .iter() + .filter(|field| !field.internal && !field.synthesized) } /// Fields that are relevant for `Construct` impl. /// - /// This includes: - /// - Fields that are not synthesized. + /// The reason why fields that are `parse` and internal are allowed is + /// because it's a pattern used a lot for parsing data from the input and + /// then storing it in a field. fn construct_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| { - !field.synthesized && (!field.internal || field.parse.is_some()) + field.parse.is_some() || (!field.synthesized && !field.internal) }) } + + /// Fields that can be configured with set rules. + fn set_fields(&self) -> impl Iterator + Clone { + self.construct_fields().filter(|field| !field.inherent()) + } + + /// Fields that can be accessed from the style chain. + fn style_fields(&self) -> impl Iterator + Clone { + self.real_fields() + .filter(|field| !field.inherent() && !field.synthesized) + } + + /// Fields that are visible to the user. + fn visible_fields(&self) -> impl Iterator + Clone { + self.real_fields().filter(|field| !field.internal && !field.ghost) + } } struct Field { @@ -132,9 +117,9 @@ struct Field { fold: bool, internal: bool, external: bool, - synthesized: bool, borrowed: bool, ghost: bool, + synthesized: bool, parse: Option, default: Option, } @@ -142,12 +127,7 @@ struct Field { impl Field { /// Whether the field is present on every instance of the element. fn inherent(&self) -> bool { - (self.required || self.variadic) && !self.ghost - } - - /// Whether the field can be used with set rules. - fn settable(&self) -> bool { - !self.inherent() + self.required || (self.synthesized && self.default.is_some()) } } @@ -191,7 +171,7 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result { }; let fields = named.named.iter().map(parse_field).collect::>>()?; - if fields.iter().any(|field| field.ghost) + if fields.iter().any(|field| field.ghost && !field.internal) && meta.capabilities.iter().all(|capability| capability != "Construct") { bail!(body.ident, "cannot have ghost fields and have `Construct` auto generated"); @@ -207,7 +187,6 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result { ident: body.ident.clone(), capabilities: meta.capabilities, fields, - enum_ident: Ident::new(&format!("{}Fields", body.ident), body.ident.span()), }) } @@ -217,7 +196,7 @@ fn parse_field(field: &syn::Field) -> Result { }; if ident == "label" { - bail!(ident, "invalid field name"); + bail!(ident, "invalid field name `label`"); } let mut attrs = field.attrs.clone(); @@ -226,43 +205,46 @@ fn parse_field(field: &syn::Field) -> Result { let positional = has_attr(&mut attrs, "positional") || required; let mut field = Field { + ident: ident.clone(), + ident_in: format_ident!("{ident}_in"), + with_ident: format_ident!("with_{ident}"), + push_ident: format_ident!("push_{ident}"), + set_ident: format_ident!("set_{ident}"), + enum_ident: Ident::new(&ident.to_string().to_upper_camel_case(), ident.span()), + const_ident: Ident::new(&ident.to_string().to_shouty_snake_case(), ident.span()), + vis: field.vis.clone(), + ty: field.ty.clone(), + output: field.ty.clone(), name: ident.to_string().to_kebab_case(), docs: documentation(&attrs), - internal: has_attr(&mut attrs, "internal"), - external: has_attr(&mut attrs, "external"), positional, required, variadic, - borrowed: has_attr(&mut attrs, "borrowed"), - synthesized: has_attr(&mut attrs, "synthesized"), - fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), + fold: has_attr(&mut attrs, "fold"), + internal: has_attr(&mut attrs, "internal"), + external: has_attr(&mut attrs, "external"), + borrowed: has_attr(&mut attrs, "borrowed"), ghost: has_attr(&mut attrs, "ghost"), + synthesized: has_attr(&mut attrs, "synthesized"), parse: parse_attr(&mut attrs, "parse")?.flatten(), default: parse_attr::(&mut attrs, "default")?.flatten(), - vis: field.vis.clone(), - ident: ident.clone(), - ident_in: Ident::new(&format!("{ident}_in"), ident.span()), - with_ident: Ident::new(&format!("with_{ident}"), ident.span()), - push_ident: Ident::new(&format!("push_{ident}"), ident.span()), - set_ident: Ident::new(&format!("set_{ident}"), ident.span()), - enum_ident: Ident::new(&ident.to_string().to_upper_camel_case(), ident.span()), - const_ident: Ident::new(&ident.to_string().to_shouty_snake_case(), ident.span()), - ty: field.ty.clone(), - output: field.ty.clone(), }; - if field.required && (field.fold || field.resolve) { - bail!(ident, "required fields cannot be folded or resolved"); - } - - if field.required && !field.positional { - bail!(ident, "only positional fields can be required"); + if field.required || field.variadic { + if !field.positional { + bail!(ident, "inherent fields must be positional"); + } else if field.fold || field.resolve || field.ghost || field.synthesized { + bail!( + ident, + "inherent fields cannot be fold, resolve, ghost, or synthesized" + ); + } } if field.resolve { - let output = &field.output; - field.output = parse_quote! { <#output as #foundations::Resolve>::Output }; + let ty = &field.ty; + field.output = parse_quote! { <#ty as #foundations::Resolve>::Output }; } validate_attrs(&attrs)?; @@ -272,57 +254,41 @@ fn parse_field(field: &syn::Field) -> Result { /// Produce the element's definition. fn create(element: &Elem) -> Result { - let Elem { vis, ident, docs, .. } = element; - - let all = element.real_fields(); - let present = element.present_fields(); - let settable = all.clone().filter(|field| !field.synthesized && field.settable()); - // The struct itself. - let derive_debug = element.cannot("Debug").then(|| quote! { #[derive(Debug)] }); - let fields = present.clone().map(create_field); + let struct_ = create_struct(element); + let inherent_impl = create_inherent_impl(element); - // Inherent functions. - let new = create_new_func(element); - let field_methods = all.clone().map(|field| create_field_method(element, field)); - let field_in_methods = - settable.clone().map(|field| create_field_in_method(element, field)); - let with_field_methods = present.clone().map(create_with_field_method); - let push_field_methods = present.clone().map(create_push_field_method); - let field_style_methods = - settable.clone().map(|field| create_set_field_method(element, field)); + // The enum with the struct's fields. + let fields_enum = create_fields_enum(element); + + // The statics with borrowed fields' default values. + let default_statics = element + .style_fields() + .filter(|field| field.borrowed) + .map(create_default_static); // Trait implementations. let native_element_impl = create_native_elem_impl(element); - let fields_impl = create_fields_impl(element); - let capable_impl = create_capable_impl(element); + let partial_eq_impl = + element.cannot("PartialEq").then(|| create_partial_eq_impl(element)); let construct_impl = element.cannot("Construct").then(|| create_construct_impl(element)); let set_impl = element.cannot("Set").then(|| create_set_impl(element)); - let partial_eq_impl = - element.cannot("PartialEq").then(|| create_partial_eq_impl(element)); + let capable_impl = create_capable_impl(element); + let fields_impl = create_fields_impl(element); let repr_impl = element.cannot("Repr").then(|| create_repr_impl(element)); let locatable_impl = element.can("Locatable").then(|| create_locatable_impl(element)); + let into_value_impl = create_into_value_impl(element); + // We use a const block to create an anonymous scope, as to not leak any + // local definitions. Ok(quote! { - #[doc = #docs] - #derive_debug - #[derive(Clone, Hash)] - #[allow(clippy::derived_hash_with_manual_eq)] - #vis struct #ident { - #(#fields,)* - } + #struct_ const _: () = { - impl #ident { - #new - #(#field_methods)* - #(#field_in_methods)* - #(#with_field_methods)* - #(#push_field_methods)* - #(#field_style_methods)* - } - + #fields_enum + #(#default_statics)* + #inherent_impl #native_element_impl #fields_impl #capable_impl @@ -331,183 +297,197 @@ fn create(element: &Elem) -> Result { #partial_eq_impl #repr_impl #locatable_impl - - impl #foundations::IntoValue for #ident { - fn into_value(self) -> #foundations::Value { - #foundations::Value::Content(#foundations::Content::new(self)) - } - } + #into_value_impl }; }) } -/// Create a field declaration. -fn create_field(field: &Field) -> TokenStream { - let Field { - ident, ty, docs, required, synthesized, default, .. - } = field; +/// Create the struct definition itself. +fn create_struct(element: &Elem) -> TokenStream { + let Elem { vis, ident, docs, .. } = element; + + let debug = element.cannot("Debug").then(|| quote! { Debug, }); + let fields = element.struct_fields().map(create_field); - let ty = required.then(|| quote! { #ty }).unwrap_or_else(|| { - if *synthesized && default.is_some() { - quote! { #ty } - } else { - quote! { ::std::option::Option<#ty> } - } - }); quote! { #[doc = #docs] - #ident: #ty + #[derive(#debug Clone, Hash)] + #[allow(clippy::derived_hash_with_manual_eq)] + #vis struct #ident { + #(#fields,)* + } + } +} + +/// Create a field declaration for the struct. +fn create_field(field: &Field) -> TokenStream { + let Field { ident, ty, .. } = field; + if field.inherent() { + quote! { #ident: #ty } + } else { + quote! { #ident: ::std::option::Option<#ty> } + } +} + +/// Creates the element's enum for field identifiers. +fn create_fields_enum(element: &Elem) -> TokenStream { + let variants: Vec<_> = element.real_fields().map(|field| &field.enum_ident).collect(); + let names: Vec<_> = element.real_fields().map(|field| &field.name).collect(); + let consts: Vec<_> = element.real_fields().map(|field| &field.const_ident).collect(); + let repr = (!variants.is_empty()).then(|| quote! { #[repr(u8)] }); + + quote! { + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] + #repr + pub enum Fields { + #(#variants,)* + } + + impl Fields { + /// Converts the field identifier to the field name. + pub fn to_str(self) -> &'static str { + match self { + #(Self::#variants => #names,)* + } + } + } + + impl ::std::convert::TryFrom for Fields { + type Error = (); + + fn try_from(value: u8) -> Result { + #(const #consts: u8 = Fields::#variants as u8;)* + match value { + #(#consts => Ok(Self::#variants),)* + _ => Err(()), + } + } + } + + impl ::std::str::FromStr for Fields { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + #(#names => Ok(Self::#variants),)* + _ => Err(()), + } + } + } + + impl ::std::fmt::Display for Fields { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.pad(self.to_str()) + } + } + } +} + +/// Creates a static with a borrowed field's default value. +fn create_default_static(field: &Field) -> TokenStream { + let Field { const_ident, default, ty, .. } = field; + + let init = match default { + Some(default) => quote! { || #default }, + None => quote! { ::std::default::Default::default }, + }; + + quote! { + static #const_ident: ::once_cell::sync::Lazy<#ty> = + ::once_cell::sync::Lazy::new(#init); + } +} + +/// Create the inherent implementation of the struct. +fn create_inherent_impl(element: &Elem) -> TokenStream { + let Elem { ident, .. } = element; + + let new_func = create_new_func(element); + let with_field_methods = element.struct_fields().map(create_with_field_method); + let push_field_methods = element.struct_fields().map(create_push_field_method); + let field_methods = element.struct_fields().map(create_field_method); + let field_in_methods = element.style_fields().map(create_field_in_method); + let set_field_methods = element.style_fields().map(create_set_field_method); + + quote! { + impl #ident { + #new_func + #(#with_field_methods)* + #(#push_field_methods)* + #(#field_methods)* + #(#field_in_methods)* + #(#set_field_methods)* + } } } /// Create the `new` function for the element. fn create_new_func(element: &Elem) -> TokenStream { - let relevant = element - .fields - .iter() - .filter(|field| !field.external && !field.synthesized && field.inherent()); - let params = relevant.clone().map(|Field { ident, ty, .. }| { - quote! { #ident: #ty } - }); - let required = relevant.map(|Field { ident, .. }| { - quote! { #ident } - }); - let defaults = element - .fields - .iter() - .filter(|field| { - !field.external && !field.inherent() && !field.synthesized && !field.ghost - }) - .map(|Field { ident, .. }| quote! { #ident: None }); - let default_synthesized = element - .fields - .iter() - .filter(|field| !field.external && field.synthesized && !field.ghost) - .map(|Field { ident, default, .. }| { + let params = element + .struct_fields() + .filter(|field| field.inherent() && !field.synthesized) + .map(|Field { ident, ty, .. }| quote! { #ident: #ty }); + + let fields = element.struct_fields().map(|field| { + let Field { ident, default, .. } = field; + if field.synthesized { if let Some(expr) = default { quote! { #ident: #expr } } else { quote! { #ident: None } } - }); + } else if field.inherent() { + quote! { #ident } + } else { + quote! { #ident: None } + } + }); quote! { - /// Create a new element. + /// Create a new instance of the element. pub fn new(#(#params),*) -> Self { - Self { - #(#required,)* - #(#defaults,)* - #(#default_synthesized,)* - } + Self { #(#fields,)* } } } } -/// Create a builder pattern method for a field. +/// Create a builder-style setter method for a field. fn create_with_field_method(field: &Field) -> TokenStream { - let Field { - vis, - ident, - with_ident, - name, - ty, - synthesized, - default, - .. - } = field; - let doc = format!("Set the [`{name}`](Self::{ident}) field."); - - let set = if field.inherent() || (*synthesized && default.is_some()) { - quote! { self.#ident = #ident; } - } else { - quote! { self.#ident = Some(#ident); } - }; + let Field { vis, ident, with_ident, push_ident, name, ty, .. } = field; + let doc = format!("Builder-style setter for the [`{name}`](Self::{ident}) field."); quote! { #[doc = #doc] #vis fn #with_ident(mut self, #ident: #ty) -> Self { - #set + self.#push_ident(#ident); self } } } -/// Create a set-style method for a field. +/// Create a setter method for a field. fn create_push_field_method(field: &Field) -> TokenStream { - let Field { - vis, - ident, - push_ident, - name, - ty, - synthesized, - default, - .. - } = field; - let doc = format!("Push the [`{name}`](Self::{ident}) field."); - let set = if (field.inherent() && !synthesized) || (*synthesized && default.is_some()) - { - quote! { self.#ident = #ident; } + let Field { vis, ident, push_ident, name, ty, .. } = field; + let doc = format!("Setter for the [`{name}`](Self::{ident}) field."); + + let expr = if field.inherent() { + quote! { #ident } } else { - quote! { self.#ident = Some(#ident); } + quote! { Some(#ident) } }; + quote! { #[doc = #doc] #vis fn #push_ident(&mut self, #ident: #ty) { - #set + self.#ident = #expr; } } } -/// Create a setter method for a field. -fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream { - let elem = &element.ident; - let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field; - let doc = format!("Create a style property for the `{name}` field."); - quote! { - #[doc = #doc] - #vis fn #set_ident(#ident: #ty) -> #foundations::Style { - #foundations::Style::Property(#foundations::Property::new( - ::elem(), - <#elem as #foundations::Fields>::Enum::#enum_ident as u8, - #ident, - )) - } - } -} +/// Create an accessor method for a field. +fn create_field_method(field: &Field) -> TokenStream { + let Field { vis, docs, ident, output, .. } = field; -/// Create a style chain access method for a field. -fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream { - let Field { vis, ident_in, name, output, .. } = field; - let doc = format!("Access the `{name}` field in the given style chain."); - let access = create_style_chain_access(element, field, quote! { None }); - - let output = if field.borrowed { - quote! { &#output } - } else { - quote! { #output } - }; - - quote! { - #[doc = #doc] - #vis fn #ident_in(styles: #foundations::StyleChain) -> #output { - #access - } - } -} - -/// Create an accessor methods for a field. -fn create_field_method(element: &Elem, field: &Field) -> TokenStream { - let Field { vis, docs, ident, output, ghost, .. } = field; - - let inherent = if *ghost { - quote! { None } - } else { - quote! { self.#ident.as_ref() } - }; - - if (field.inherent() && !field.synthesized) - || (field.synthesized && field.default.is_some()) - { + if field.inherent() { quote! { #[doc = #docs] #vis fn #ident(&self) -> &#output { @@ -522,36 +502,66 @@ fn create_field_method(element: &Elem, field: &Field) -> TokenStream { self.#ident.as_ref().unwrap() } } - } else if field.borrowed { - let access = create_style_chain_access(element, field, inherent); - - quote! { - #[doc = #docs] - #vis fn #ident<'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output { - #access - } - } } else { - let access = create_style_chain_access(element, field, inherent); + let sig = if field.borrowed { + quote! { <'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output } + } else { + quote! { (&self, styles: #foundations::StyleChain) -> #output } + }; + + let mut value = create_style_chain_access(field, quote! { self.#ident.as_ref() }); + if field.resolve { + value = quote! { #foundations::Resolve::resolve(#value, styles) }; + } quote! { #[doc = #docs] - #vis fn #ident(&self, styles: #foundations::StyleChain) -> #output { - #access + #vis fn #ident #sig { + #value } } } } -/// Create a style chain access method for a field. -fn create_style_chain_access( - element: &Elem, - field: &Field, - inherent: TokenStream, -) -> TokenStream { - let elem = &element.ident; +/// Create a style accessor method for a field. +fn create_field_in_method(field: &Field) -> TokenStream { + let Field { vis, ident_in, name, output, .. } = field; + let doc = format!("Access the `{name}` field in the given style chain."); - let Field { ty, default, enum_ident, .. } = field; + let ref_ = field.borrowed.then(|| quote! { & }); + + let mut value = create_style_chain_access(field, quote! { None }); + if field.resolve { + value = quote! { #foundations::Resolve::resolve(#value, styles) }; + } + + quote! { + #[doc = #doc] + #vis fn #ident_in(styles: #foundations::StyleChain) -> #ref_ #output { + #value + } + } +} + +/// Create a style setter method for a field. +fn create_set_field_method(field: &Field) -> TokenStream { + let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field; + let doc = format!("Create a style property for the `{name}` field."); + + quote! { + #[doc = #doc] + #vis fn #set_ident(#ident: #ty) -> #foundations::Property { + #foundations::Property::new::( + Fields::#enum_ident as u8, + #ident, + ) + } + } +} + +/// Create a style chain access method for a field. +fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream { + let Field { ty, default, enum_ident, const_ident, .. } = field; let getter = match (field.fold, field.borrowed) { (false, false) => quote! { get }, @@ -559,47 +569,34 @@ fn create_style_chain_access( (true, _) => quote! { get_folded }, }; - let mut default = match default { - Some(default) => quote! { #default }, - None => quote! { ::std::default::Default::default() }, + let default = if field.borrowed { + quote! { || &#const_ident } + } else { + match default { + Some(default) => quote! { || #default }, + None => quote! { ::std::default::Default::default }, + } }; - let mut init = None; - if field.borrowed { - init = Some(quote! { - static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default); - }); - default = quote! { &DEFAULT }; - } - - let mut value = quote! { - styles.#getter::<#ty>( - ::elem(), - <#elem as #foundations::Fields>::Enum::#enum_ident as u8, - #inherent, - || #default, - ) - }; - - if field.resolve { - value = quote! { #foundations::Resolve::resolve(#value, styles) }; - } - quote! { - #init - #value + styles.#getter::<#ty>( + ::elem(), + Fields::#enum_ident as u8, + #inherent, + #default, + ) } } -/// Creates the element's `Pack` implementation. +/// Creates the element's `NativeElement` implementation. fn create_native_elem_impl(element: &Elem) -> TokenStream { let Elem { name, ident, title, scope, keywords, docs, .. } = element; - let params = element - .fields - .iter() - .filter(|field| !field.internal && !field.synthesized) - .map(create_param_info); + let local_name = if element.can("LocalName") { + quote! { Some(<#foundations::Packed<#ident> as ::typst::text::LocalName>::local_name) } + } else { + quote! { None } + }; let scope = if *scope { quote! { <#ident as #foundations::NativeScope>::scope() } @@ -607,11 +604,7 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { quote! { #foundations::Scope::new() } }; - let local_name = if element.can("LocalName") { - quote! { Some(<#foundations::Packed<#ident> as ::typst::text::LocalName>::local_name) } - } else { - quote! { None } - }; + let params = element.doc_fields().map(create_param_info); let data = quote! { #foundations::NativeElementData { @@ -622,12 +615,8 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { construct: <#ident as #foundations::Construct>::construct, set: <#ident as #foundations::Set>::set, vtable: <#ident as #foundations::Capable>::vtable, - field_id: |name| - <<#ident as #foundations::Fields>::Enum as ::std::str::FromStr> - ::from_str(name).ok().map(|id| id as u8), - field_name: |id| - <<#ident as #foundations::Fields>::Enum as ::std::convert::TryFrom> - ::try_from(id).ok().map(<#ident as #foundations::Fields>::Enum::to_str), + field_id: |name| name.parse().ok().map(|id: Fields| id as u8), + field_name: |id| id.try_into().ok().map(Fields::to_str), local_name: #local_name, scope: #foundations::Lazy::new(|| #scope), params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]) @@ -644,201 +633,65 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { } } -fn create_fields_impl(element: &Elem) -> TokenStream { - let Elem { ident, enum_ident, .. } = element; +/// Creates a parameter info for a field. +fn create_param_info(field: &Field) -> TokenStream { + let Field { + name, + docs, + positional, + variadic, + required, + default, + ty, + .. + } = field; - let fields_enum = create_fields_enum(element); + let named = !positional; + let settable = !field.inherent(); - // Fields that can be checked using the `has` method. - let field_has_matches = element.visible_fields().map(|field| { - let elem = &element.ident; - let name = &field.enum_ident; - let field_ident = &field.ident; - - if field.ghost { - quote! { - <#elem as #foundations::Fields>::Enum::#name => false, - } - } else if field.inherent() || (field.synthesized && field.default.is_some()) { - quote! { - <#elem as #foundations::Fields>::Enum::#name => true, - } - } else { - quote! { - <#elem as #foundations::Fields>::Enum::#name => self.#field_ident.is_some(), - } + let default = if settable { + let default = default + .clone() + .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); + quote! { + Some(|| <#ty as #foundations::IntoValue>::into_value(#default)) } - }); + } else { + quote! { None } + }; - // Creation of the fields dictionary for inherent fields. - let field_dict = element - .inherent_fields() - .filter(|field| !field.internal) - .clone() - .map(|field| { - let name = &field.name; - let field_ident = &field.ident; - let field_call = quote! { ::ecow::EcoString::from(#name).into() }; - quote! { - fields.insert( - #field_call, - #foundations::IntoValue::into_value(self.#field_ident.clone()) - ); - } - }); - - // Creation of the fields dictionary for optional fields. - let field_opt_dict = element - .visible_fields() - .filter(|field| !field.inherent() && !field.ghost) - .clone() - .map(|field| { - let name = &field.name; - let field_ident = &field.ident; - let field_call = quote! { ::ecow::EcoString::from(#name).into() }; - if field.synthesized && field.default.is_some() { - quote! { - fields.insert( - #field_call, - #foundations::IntoValue::into_value(self.#field_ident.clone()) - ); - } - } else { - quote! { - if let Some(value) = &self.#field_ident { - fields.insert( - #field_call, - #foundations::IntoValue::into_value(value.clone()) - ); - } - } - } - }); - - // Fields that can be accessed using the `field` method. - let field_matches = element.visible_fields().map(|field| { - let elem = &element.ident; - let name = &field.enum_ident; - let field_ident = &field.ident; - - if field.ghost { - quote! { - <#elem as #foundations::Fields>::Enum::#name => None, - } - } else if field.inherent() || (field.synthesized && field.default.is_some()) { - quote! { - <#elem as #foundations::Fields>::Enum::#name => Some( - #foundations::IntoValue::into_value(self.#field_ident.clone()) - ), - } - } else { - quote! { - <#elem as #foundations::Fields>::Enum::#name => { - self.#field_ident.clone().map(#foundations::IntoValue::into_value) - } - } - } - }); + let ty = if *variadic { + quote! { <#ty as #foundations::Container>::Inner } + } else { + quote! { #ty } + }; quote! { - #fields_enum - - impl #foundations::Fields for #ident { - type Enum = #enum_ident; - - fn has(&self, id: u8) -> bool { - let Ok(id) = <#ident as #foundations::Fields>::Enum::try_from(id) else { - return false; - }; - - match id { - #(#field_has_matches)* - _ => false, - } - } - - fn field(&self, id: u8) -> Option<#foundations::Value> { - let id = <#ident as #foundations::Fields>::Enum::try_from(id).ok()?; - match id { - #(#field_matches)* - _ => None, - } - } - - fn fields(&self) -> #foundations::Dict { - let mut fields = #foundations::Dict::new(); - #(#field_dict)* - #(#field_opt_dict)* - fields - } + #foundations::ParamInfo { + name: #name, + docs: #docs, + input: <#ty as #foundations::Reflect>::input(), + default: #default, + positional: #positional, + named: #named, + variadic: #variadic, + required: #required, + settable: #settable, } } } -/// Creates the element's enum for field identifiers. -fn create_fields_enum(element: &Elem) -> TokenStream { - let Elem { enum_ident, .. } = element; - - let fields = element.real_fields().collect::>(); - let field_names = fields.iter().map(|Field { name, .. }| name).collect::>(); - let field_consts = fields - .iter() - .map(|Field { const_ident, .. }| const_ident) - .collect::>(); - - let field_variants = fields - .iter() - .map(|Field { enum_ident, .. }| enum_ident) - .collect::>(); - - let definitions = fields.iter().map(|Field { enum_ident, .. }| { - quote! { #enum_ident } - }); - - let enum_repr = (!fields.is_empty()).then(|| quote! { #[repr(u8)] }); +/// Creates the element's `PartialEq` implementation. +fn create_partial_eq_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let empty = element.eq_fields().next().is_none().then(|| quote! { true }); + let fields = element.eq_fields().map(|field| &field.ident); quote! { - #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] - #enum_repr - pub enum #enum_ident { - #(#definitions,)* - } - - impl #enum_ident { - /// Converts this field identifier to the field name. - pub fn to_str(self) -> &'static str { - match self { - #(Self::#field_variants => #field_names,)* - } - } - } - - impl ::std::convert::TryFrom for #enum_ident { - type Error = (); - - fn try_from(value: u8) -> Result { - #(const #field_consts: u8 = #enum_ident::#field_variants as u8;)* - match value { - #(#field_consts => Ok(Self::#field_variants),)* - _ => Err(()), - } - } - } - - impl ::std::fmt::Display for #enum_ident { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - f.pad(self.to_str()) - } - } - - impl ::std::str::FromStr for #enum_ident { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - #(#field_names => Ok(Self::#field_variants),)* - _ => Err(()), - } + impl PartialEq for #ident { + fn eq(&self, other: &Self) -> bool { + #empty + #(self.#fields == other.#fields)&&* } } } @@ -847,7 +700,7 @@ fn create_fields_enum(element: &Elem) -> TokenStream { /// Creates the element's `Construct` implementation. fn create_construct_impl(element: &Elem) -> TokenStream { let ident = &element.ident; - let pre = element.construct_fields().map(|field| { + let setup = element.construct_fields().map(|field| { let (prefix, value) = create_field_parser(field); let ident = &field.ident; quote! { @@ -856,24 +709,18 @@ fn create_construct_impl(element: &Elem) -> TokenStream { } }); - let handlers = - element - .construct_fields() - .filter(|field| field.settable()) - .map(|field| { - let push_ident = &field.push_ident; - let ident = &field.ident; - quote! { - if let Some(value) = #ident { - element.#push_ident(value); - } - } - }); - - let defaults = element - .construct_fields() - .filter(|field| !field.settable()) - .map(|field| &field.ident); + let fields = element.struct_fields().map(|field| { + let Field { ident, default, .. } = field; + if field.synthesized { + if let Some(expr) = default { + quote! { #ident: #expr } + } else { + quote! { #ident: None } + } + } else { + quote! { #ident } + } + }); quote! { impl #foundations::Construct for #ident { @@ -881,13 +728,8 @@ fn create_construct_impl(element: &Elem) -> TokenStream { engine: &mut ::typst::engine::Engine, args: &mut #foundations::Args, ) -> ::typst::diag::SourceResult<#foundations::Content> { - #(#pre)* - - let mut element = Self::new(#(#defaults),*); - - #(#handlers)* - - Ok(#foundations::Content::new(element)) + #(#setup)* + Ok(#foundations::Content::new(Self { #(#fields),* })) } } } @@ -896,7 +738,7 @@ fn create_construct_impl(element: &Elem) -> TokenStream { /// Creates the element's `Set` implementation. fn create_set_impl(element: &Elem) -> TokenStream { let ident = &element.ident; - let handlers = element.settable_fields().map(|field| { + let handlers = element.set_fields().map(|field| { let set_ident = &field.set_ident; let (prefix, value) = create_field_parser(field); quote! { @@ -921,55 +763,38 @@ fn create_set_impl(element: &Elem) -> TokenStream { } } -/// Creates the element's `Locatable` implementation. -fn create_locatable_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - quote! { impl ::typst::introspection::Locatable for #foundations::Packed<#ident> {} } -} - -/// Creates the element's `PartialEq` implementation. -fn create_partial_eq_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let all = element.eq_fields().map(|field| &field.ident).collect::>(); - - let empty = all.is_empty().then(|| quote! { true }); - quote! { - impl PartialEq for #ident { - fn eq(&self, other: &Self) -> bool { - #empty - #(self.#all == other.#all)&&* - } - } +/// Create argument parsing code for a field. +fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { + if let Some(BlockWithReturn { prefix, expr }) = &field.parse { + return (quote! { #(#prefix);* }, quote! { #expr }); } -} -/// Creates the element's `Repr` implementation. -fn create_repr_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let repr_format = format!("{}{{}}", element.name); - quote! { - impl #foundations::Repr for #ident { - fn repr(&self) -> ::ecow::EcoString { - let fields = #foundations::Fields::fields(self).into_iter() - .map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr())) - .collect::>(); - ::ecow::eco_format!(#repr_format, #foundations::repr::pretty_array_like(&fields, false)) - } - } - } + let name = &field.name; + let value = if field.variadic { + quote! { args.all()? } + } else if field.required { + quote! { args.expect(#name)? } + } else if field.positional { + quote! { args.find()? } + } else { + quote! { args.named(#name)? } + }; + + (quote! {}, value) } /// Creates the element's casting vtable. fn create_capable_impl(element: &Elem) -> TokenStream { // Forbidden capabilities (i.e capabilities that are not object safe). const FORBIDDEN: &[&str] = - &["Construct", "PartialEq", "Hash", "LocalName", "Repr", "Debug"]; + &["Debug", "PartialEq", "Hash", "Construct", "Set", "Repr", "LocalName"]; let ident = &element.ident; let relevant = element .capabilities .iter() .filter(|&ident| !FORBIDDEN.contains(&(&ident.to_string() as &str))); + let checks = relevant.map(|capability| { quote! { if capability == ::std::any::TypeId::of::() { @@ -993,67 +818,122 @@ fn create_capable_impl(element: &Elem) -> TokenStream { } } -/// Creates a parameter info for a field. -fn create_param_info(field: &Field) -> TokenStream { - let Field { - name, - docs, - positional, - variadic, - required, - default, - ty, - .. - } = field; - let named = !positional; - let settable = field.settable(); - let default = quote_option(&settable.then(|| { - let default = default - .clone() - .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); - quote! { - || { - let typed: #ty = #default; - #foundations::IntoValue::into_value(typed) +/// Creates the element's `Fields` implementation. +fn create_fields_impl(element: &Elem) -> TokenStream { + let into_value = quote! { #foundations::IntoValue::into_value }; + + // Fields that can be checked using the `has` method. + let has_arms = element.visible_fields().map(|field| { + let Field { enum_ident, ident, .. } = field; + + let expr = if field.inherent() { + quote! { true } + } else { + quote! { self.#ident.is_some() } + }; + + quote! { Fields::#enum_ident => #expr } + }); + + // Fields that can be accessed using the `field` method. + let field_arms = element.visible_fields().map(|field| { + let Field { enum_ident, ident, .. } = field; + + let expr = if field.inherent() { + quote! { Some(#into_value(self.#ident.clone())) } + } else { + quote! { self.#ident.clone().map(#into_value) } + }; + + quote! { Fields::#enum_ident => #expr } + }); + + // Creation of the `fields` dictionary for inherent fields. + let field_inserts = element.visible_fields().map(|field| { + let Field { ident, name, .. } = field; + let string = quote! { #name.into() }; + + if field.inherent() { + quote! { + fields.insert( #string, #into_value(self.#ident.clone())); + } + } else { + quote! { + if let Some(value) = &self.#ident { + fields.insert(#string, #into_value(value.clone())); + } } } - })); - let ty = if *variadic { - quote! { <#ty as #foundations::Container>::Inner } - } else { - quote! { #ty } - }; + }); + + let Elem { ident, .. } = element; + quote! { - #foundations::ParamInfo { - name: #name, - docs: #docs, - input: <#ty as #foundations::Reflect>::input(), - default: #default, - positional: #positional, - named: #named, - variadic: #variadic, - required: #required, - settable: #settable, + impl #foundations::Fields for #ident { + type Enum = Fields; + + fn has(&self, id: u8) -> bool { + let Ok(id) = <#ident as #foundations::Fields>::Enum::try_from(id) else { + return false; + }; + + match id { + #(#has_arms,)* + _ => false, + } + } + + fn field(&self, id: u8) -> Option<#foundations::Value> { + let id = <#ident as #foundations::Fields>::Enum::try_from(id).ok()?; + match id { + #(#field_arms,)* + _ => None, + } + } + + fn fields(&self) -> #foundations::Dict { + let mut fields = #foundations::Dict::new(); + #(#field_inserts)* + fields + } } } } -/// Create argument parsing code for a field. -fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { - if let Some(BlockWithReturn { prefix, expr }) = &field.parse { - return (quote! { #(#prefix);* }, quote! { #expr }); +/// Creates the element's `Repr` implementation. +fn create_repr_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + let repr_format = format!("{}{{}}", element.name); + quote! { + impl #foundations::Repr for #ident { + fn repr(&self) -> ::ecow::EcoString { + let fields = #foundations::Fields::fields(self) + .into_iter() + .map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr())) + .collect::>(); + ::ecow::eco_format!( + #repr_format, + #foundations::repr::pretty_array_like(&fields, false), + ) + } + } + } +} + +/// Creates the element's `Locatable` implementation. +fn create_locatable_impl(element: &Elem) -> TokenStream { + let ident = &element.ident; + quote! { impl ::typst::introspection::Locatable for #foundations::Packed<#ident> {} } +} + +/// Creates the element's `IntoValue` implementation. +fn create_into_value_impl(element: &Elem) -> TokenStream { + let Elem { ident, .. } = element; + quote! { + impl #foundations::IntoValue for #ident { + fn into_value(self) -> #foundations::Value { + #foundations::Value::Content(#foundations::Content::new(self)) + } + } } - - let name = &field.name; - let value = if field.variadic { - quote! { args.all()? } - } else if field.required { - quote! { args.expect(#name)? } - } else if field.positional { - quote! { args.find()? } - } else { - quote! { args.named(#name)? } - }; - - (quote! {}, value) } diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs index 83279a7b1..bac92887b 100644 --- a/crates/typst/src/foundations/styles.rs +++ b/crates/typst/src/foundations/styles.rs @@ -229,11 +229,17 @@ pub struct Property { impl Property { /// Create a new property from a key-value pair. - pub fn new(elem: Element, id: u8, value: T) -> Self + pub fn new(id: u8, value: T) -> Self where + E: NativeElement, T: Debug + Clone + Hash + Send + Sync + 'static, { - Self { elem, id, value: Block::new(value), span: None } + Self { + elem: E::elem(), + id, + value: Block::new(value), + span: None, + } } /// Whether this property is the given one. @@ -245,6 +251,11 @@ impl Property { pub fn is_of(&self, elem: Element) -> bool { self.elem == elem } + + /// Turn this property into prehashed style. + pub fn wrap(self) -> Prehashed