summaryrefslogtreecommitdiff
path: root/crates/typst-macros/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-01-30 14:49:51 +0100
committerGitHub <noreply@github.com>2024-01-30 13:49:51 +0000
commita1e8560ca65cefaa423959ef3f2881b23528de8d (patch)
treec5db0d765fe70f0b9916b6639d61a0f76320d695 /crates/typst-macros/src
parentf14288cacf7331a422cb447d60d529797def3950 (diff)
Refactor `#[elem]` macro (#3303)
Diffstat (limited to 'crates/typst-macros/src')
-rw-r--r--crates/typst-macros/src/elem.rs1096
1 files changed, 488 insertions, 608 deletions
diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs
index 91147b8d..a3e2a007 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<Ident>,
fields: Vec<Field>,
}
@@ -43,72 +41,59 @@ 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<Item = &Field> + 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<Item = &Field> + Clone {
+ /// Fields that are present in the generated struct.
+ fn struct_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.real_fields().filter(|field| !field.ghost)
}
- /// Fields that are inherent to the element.
- fn inherent_fields(&self) -> impl Iterator<Item = &Field> + Clone {
- self.real_fields().filter(|field| field.inherent())
+ /// Fields that are relevant for equality.
+ ///
+ /// Synthesized fields are excluded to ensure equality before and after
+ /// synthesis.
+ fn eq_fields(&self) -> impl Iterator<Item = &Field> + Clone {
+ self.struct_fields().filter(|field| !field.synthesized)
}
- /// 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.
+ /// Fields that show up in the documentation.
+ fn doc_fields(&self) -> impl Iterator<Item = &Field> + Clone {
+ self.fields
+ .iter()
+ .filter(|field| !field.internal && !field.synthesized)
+ }
+
+ /// Fields that are relevant for `Construct` impl.
///
- /// 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<Item = &Field> + Clone {
+ /// 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<Item = &Field> + Clone {
self.real_fields().filter(|field| {
- !field.synthesized
- && field.settable()
- && (!field.internal || field.parse.is_some())
+ field.parse.is_some() || (!field.synthesized && !field.internal)
})
}
- /// Fields that are visible to the user.
- ///
- /// This includes:
- /// - Fields that are not internal.
- fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone {
- self.real_fields().filter(|field| !field.internal)
+ /// Fields that can be configured with set rules.
+ fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone {
+ self.construct_fields().filter(|field| !field.inherent())
}
- /// Fields that are relevant for equality.
- ///
- /// This includes:
- /// - Fields that are not synthesized (guarantees equality before and after synthesis).
- fn eq_fields(&self) -> impl Iterator<Item = &Field> + Clone {
- self.present_fields().filter(|field| !field.synthesized)
+ /// Fields that can be accessed from the style chain.
+ fn style_fields(&self) -> impl Iterator<Item = &Field> + Clone {
+ self.real_fields()
+ .filter(|field| !field.inherent() && !field.synthesized)
}
- /// Fields that are relevant for `Construct` impl.
- ///
- /// This includes:
- /// - Fields that are not synthesized.
- fn construct_fields(&self) -> impl Iterator<Item = &Field> + Clone {
- self.real_fields().filter(|field| {
- !field.synthesized && (!field.internal || field.parse.is_some())
- })
+ /// Fields that are visible to the user.
+ fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone {
+ self.real_fields().filter(|field| !field.internal && !field.ghost)
}
}
@@ -132,9 +117,9 @@ struct Field {
fold: bool,
internal: bool,
external: bool,
- synthesized: bool,
borrowed: bool,
ghost: bool,
+ synthesized: bool,
parse: Option<BlockWithReturn>,
default: Option<syn::Expr>,
}
@@ -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<Elem> {
};
let fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?;
- 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<Elem> {
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<Field> {
};
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<Field> {
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::<syn::Expr>(&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<Field> {
/// Produce the element's definition.
fn create(element: &Elem) -> Result<TokenStream> {
- let Elem { vis, ident, docs, .. } = element;
+ // The struct itself.
+ let struct_ = create_struct(element);
+ let inherent_impl = create_inherent_impl(element);
- let all = element.real_fields();
- let present = element.present_fields();
- let settable = all.clone().filter(|field| !field.synthesized && field.settable());
+ // The enum with the struct's fields.
+ let fields_enum = create_fields_enum(element);
- // The struct itself.
- let derive_debug = element.cannot("Debug").then(|| quote! { #[derive(Debug)] });
- let fields = present.clone().map(create_field);
-
- // 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 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<TokenStream> {
#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 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, .. }| {
+/// Create a field declaration for the struct.
+fn create_field(field: &Field) -> TokenStream {
+ let Field { ident, ty, .. } = field;
+ if field.inherent() {
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, .. }| {
- if let Some(expr) = default {
- quote! { #ident: #expr }
- } else {
- quote! { #ident: None }
- }
- });
+ } 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! {
- /// Create a new element.
- pub fn new(#(#params),*) -> Self {
- Self {
- #(#required,)*
- #(#defaults,)*
- #(#default_synthesized,)*
+ #[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<u8> for Fields {
+ type Error = ();
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ #(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<Self, Self::Err> {
+ 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())
}
}
}
}
-/// Create a builder pattern 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.");
+/// 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 set = if field.inherent() || (*synthesized && default.is_some()) {
- quote! { self.#ident = #ident; }
- } else {
- quote! { self.#ident = Some(#ident); }
+ let init = match default {
+ Some(default) => quote! { || #default },
+ None => quote! { ::std::default::Default::default },
};
+
quote! {
- #[doc = #doc]
- #vis fn #with_ident(mut self, #ident: #ty) -> Self {
- #set
- self
+ 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 a set-style 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; }
- } else {
- quote! { self.#ident = Some(#ident); }
- };
+/// Create the `new` function for the element.
+fn create_new_func(element: &Elem) -> TokenStream {
+ 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! {
- #[doc = #doc]
- #vis fn #push_ident(&mut self, #ident: #ty) {
- #set
+ /// Create a new instance of the element.
+ pub fn new(#(#params),*) -> Self {
+ Self { #(#fields,)* }
}
}
}
-/// 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.");
+/// Create a builder-style setter method for a field.
+fn create_with_field_method(field: &Field) -> TokenStream {
+ 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 #set_ident(#ident: #ty) -> #foundations::Style {
- #foundations::Style::Property(#foundations::Property::new(
- <Self as #foundations::NativeElement>::elem(),
- <#elem as #foundations::Fields>::Enum::#enum_ident as u8,
- #ident,
- ))
+ #vis fn #with_ident(mut self, #ident: #ty) -> Self {
+ self.#push_ident(#ident);
+ self
}
}
}
-/// 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 });
+/// Create a setter method for a field.
+fn create_push_field_method(field: &Field) -> TokenStream {
+ let Field { vis, ident, push_ident, name, ty, .. } = field;
+ let doc = format!("Setter for the [`{name}`](Self::{ident}) field.");
- let output = if field.borrowed {
- quote! { &#output }
+ let expr = if field.inherent() {
+ quote! { #ident }
} else {
- quote! { #output }
+ quote! { Some(#ident) }
};
quote! {
#[doc = #doc]
- #vis fn #ident_in(styles: #foundations::StyleChain) -> #output {
- #access
+ #vis fn #push_ident(&mut self, #ident: #ty) {
+ self.#ident = #expr;
}
}
}
-/// Create an accessor methods for a field.
-fn create_field_method(element: &Elem, field: &Field) -> TokenStream {
- let Field { vis, docs, ident, output, ghost, .. } = field;
+/// Create an accessor method for a field.
+fn create_field_method(field: &Field) -> TokenStream {
+ let Field { vis, docs, ident, output, .. } = 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);
+ } else {
+ let sig = if field.borrowed {
+ quote! { <'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output }
+ } else {
+ quote! { (&self, styles: #foundations::StyleChain) -> #output }
+ };
- quote! {
- #[doc = #docs]
- #vis fn #ident<'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output {
- #access
- }
+ let mut value = create_style_chain_access(field, quote! { self.#ident.as_ref() });
+ if field.resolve {
+ value = quote! { #foundations::Resolve::resolve(#value, styles) };
}
- } else {
- let access = create_style_chain_access(element, field, inherent);
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::<Self, _>(
+ 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! {
+ quote! {
styles.#getter::<#ty>(
<Self as #foundations::NativeElement>::elem(),
- <#elem as #foundations::Fields>::Enum::#enum_ident as u8,
+ Fields::#enum_ident as u8,
#inherent,
- || #default,
+ #default,
)
- };
-
- if field.resolve {
- value = quote! { #foundations::Resolve::resolve(#value, styles) };
- }
-
- quote! {
- #init
- #value
}
}
-/// 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<u8>>
- ::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;
-
- let fields_enum = create_fields_enum(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;
- // 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;
+ let named = !positional;
+ let settable = !field.inherent();
- 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))
}
- });
-
- // 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;
+ } else {
+ quote! { None }
+ };
- 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::<Vec<_>>();
- let field_names = fields.iter().map(|Field { name, .. }| name).collect::<Vec<_>>();
- let field_consts = fields
- .iter()
- .map(|Field { const_ident, .. }| const_ident)
- .collect::<Vec<_>>();
-
- let field_variants = fields
- .iter()
- .map(|Field { enum_ident, .. }| enum_ident)
- .collect::<Vec<_>>();
-
- 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<u8> for #enum_ident {
- type Error = ();
-
- fn try_from(value: u8) -> Result<Self, Self::Error> {
- #(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<Self, Self::Err> {
- 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::<Vec<_>>();
-
- 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::<Vec<_>>();
- ::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::<dyn #capability>() {
@@ -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::<Vec<_>>();
+ ::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)? }
- };
+/// 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> {} }
+}
- (quote! {}, value)
+/// 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))
+ }
+ }
+ }
}