Merge Fields and ElementFields traits

This commit is contained in:
Laurenz 2024-01-29 12:37:55 +01:00
parent 1b3ff2dd78
commit 03079887f3
3 changed files with 129 additions and 135 deletions

View File

@ -201,7 +201,13 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
let syn::Fields::Named(named) = &body.fields else { let syn::Fields::Named(named) = &body.fields else {
bail!(body, "expected named fields"); bail!(body, "expected named fields");
}; };
let fields = named.named.iter().map(parse_field).collect::<Result<_>>()?;
let fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?;
if fields.iter().any(|field| field.ghost)
&& meta.capabilities.iter().all(|capability| capability != "Construct")
{
bail!(body.ident, "cannot have ghost fields and have `Construct` auto generated");
}
Ok(Elem { Ok(Elem {
name, name,
@ -285,22 +291,14 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
fn create(element: &Elem) -> Result<TokenStream> { fn create(element: &Elem) -> Result<TokenStream> {
let Elem { vis, ident, docs, .. } = element; let Elem { vis, ident, docs, .. } = element;
if element.fields.iter().any(|field| field.ghost)
&& element
.capabilities
.iter()
.all(|capability| capability != "Construct")
{
bail!(ident, "cannot have ghost fields and have `Construct` auto generated");
}
let all = element.real_fields(); let all = element.real_fields();
let present = element.present_fields(); let present = element.present_fields();
let settable = all.clone().filter(|field| !field.synthesized && field.settable()); let settable = all.clone().filter(|field| !field.synthesized && field.settable());
// The struct itself.
let fields = present.clone().map(create_field); let fields = present.clone().map(create_field);
let fields_enum = create_fields_enum(element);
// Inherent functions.
let new = create_new_func(element); let new = create_new_func(element);
let field_methods = all.clone().map(|field| create_field_method(element, field)); let field_methods = all.clone().map(|field| create_field_method(element, field));
let field_in_methods = let field_in_methods =
@ -333,31 +331,31 @@ fn create(element: &Elem) -> Result<TokenStream> {
#(#fields,)* #(#fields,)*
} }
#fields_enum const _: () = {
impl #ident {
impl #ident { #new
#new #(#field_methods)*
#(#field_methods)* #(#field_in_methods)*
#(#field_in_methods)* #(#with_field_methods)*
#(#with_field_methods)* #(#push_field_methods)*
#(#push_field_methods)* #(#field_style_methods)*
#(#field_style_methods)*
}
#native_element_impl
#fields_impl
#capable_impl
#construct_impl
#set_impl
#locatable_impl
#partial_eq_impl
#repr_impl
impl #foundations::IntoValue for #ident {
fn into_value(self) -> #foundations::Value {
#foundations::Value::Content(#foundations::Content::new(self))
} }
}
#native_element_impl
#fields_impl
#capable_impl
#construct_impl
#set_impl
#locatable_impl
#partial_eq_impl
#repr_impl
impl #foundations::IntoValue for #ident {
fn into_value(self) -> #foundations::Value {
#foundations::Value::Content(#foundations::Content::new(self))
}
}
};
}) })
} }
@ -380,82 +378,6 @@ fn create_field(field: &Field) -> TokenStream {
} }
} }
/// Creates the element's enum for field identifiers.
fn create_fields_enum(element: &Elem) -> TokenStream {
let Elem { ident, 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)] });
quote! {
// To hide the private type
const _: () = {
impl #foundations::ElementFields for #ident {
type Fields = #enum_ident;
}
#[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(()),
}
}
}
};
}
}
/// Create the `new` function for the element. /// Create the `new` function for the element.
fn create_new_func(element: &Elem) -> TokenStream { fn create_new_func(element: &Elem) -> TokenStream {
let relevant = element let relevant = element
@ -564,7 +486,7 @@ fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream {
#vis fn #set_ident(#ident: #ty) -> #foundations::Style { #vis fn #set_ident(#ident: #ty) -> #foundations::Style {
#foundations::Style::Property(#foundations::Property::new( #foundations::Style::Property(#foundations::Property::new(
<Self as #foundations::NativeElement>::elem(), <Self as #foundations::NativeElement>::elem(),
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8, <#elem as #foundations::Fields>::Enum::#enum_ident as u8,
#ident, #ident,
)) ))
} }
@ -670,7 +592,7 @@ fn create_style_chain_access(
#init #init
styles.#getter::<#ty>( styles.#getter::<#ty>(
<Self as #foundations::NativeElement>::elem(), <Self as #foundations::NativeElement>::elem(),
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8, <#elem as #foundations::Fields>::Enum::#enum_ident as u8,
#inherent, #inherent,
#default, #default,
) )
@ -710,13 +632,11 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
set: <#ident as #foundations::Set>::set, set: <#ident as #foundations::Set>::set,
vtable: <#ident as #foundations::Capable>::vtable, vtable: <#ident as #foundations::Capable>::vtable,
field_id: |name| field_id: |name|
< <<#ident as #foundations::Fields>::Enum as ::std::str::FromStr>
<#ident as #foundations::ElementFields>::Fields as ::std::str::FromStr ::from_str(name).ok().map(|id| id as u8),
>::from_str(name).ok().map(|id| id as u8),
field_name: |id| field_name: |id|
< <<#ident as #foundations::Fields>::Enum as ::std::convert::TryFrom<u8>>
<#ident as #foundations::ElementFields>::Fields as ::std::convert::TryFrom<u8> ::try_from(id).ok().map(<#ident as #foundations::Fields>::Enum::to_str),
>::try_from(id).ok().map(<#ident as #foundations::ElementFields>::Fields::to_str),
local_name: #local_name, local_name: #local_name,
scope: #foundations::Lazy::new(|| #scope), scope: #foundations::Lazy::new(|| #scope),
params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]) params: #foundations::Lazy::new(|| ::std::vec![#(#params),*])
@ -734,7 +654,9 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
} }
fn create_fields_impl(element: &Elem) -> TokenStream { fn create_fields_impl(element: &Elem) -> TokenStream {
let Elem { ident, .. } = element; let Elem { ident, enum_ident, .. } = element;
let fields_enum = create_fields_enum(element);
// Fields that can be checked using the `has` method. // Fields that can be checked using the `has` method.
let field_has_matches = element.visible_fields().map(|field| { let field_has_matches = element.visible_fields().map(|field| {
@ -744,15 +666,15 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
if field.ghost { if field.ghost {
quote! { quote! {
<#elem as #foundations::ElementFields>::Fields::#name => false, <#elem as #foundations::Fields>::Enum::#name => false,
} }
} else if field.inherent() || (field.synthesized && field.default.is_some()) { } else if field.inherent() || (field.synthesized && field.default.is_some()) {
quote! { quote! {
<#elem as #foundations::ElementFields>::Fields::#name => true, <#elem as #foundations::Fields>::Enum::#name => true,
} }
} else { } else {
quote! { quote! {
<#elem as #foundations::ElementFields>::Fields::#name => self.#field_ident.is_some(), <#elem as #foundations::Fields>::Enum::#name => self.#field_ident.is_some(),
} }
} }
}); });
@ -810,17 +732,17 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
if field.ghost { if field.ghost {
quote! { quote! {
<#elem as #foundations::ElementFields>::Fields::#name => None, <#elem as #foundations::Fields>::Enum::#name => None,
} }
} else if field.inherent() || (field.synthesized && field.default.is_some()) { } else if field.inherent() || (field.synthesized && field.default.is_some()) {
quote! { quote! {
<#elem as #foundations::ElementFields>::Fields::#name => Some( <#elem as #foundations::Fields>::Enum::#name => Some(
#foundations::IntoValue::into_value(self.#field_ident.clone()) #foundations::IntoValue::into_value(self.#field_ident.clone())
), ),
} }
} else { } else {
quote! { quote! {
<#elem as #foundations::ElementFields>::Fields::#name => { <#elem as #foundations::Fields>::Enum::#name => {
self.#field_ident.clone().map(#foundations::IntoValue::into_value) self.#field_ident.clone().map(#foundations::IntoValue::into_value)
} }
} }
@ -828,9 +750,13 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
}); });
quote! { quote! {
#fields_enum
impl #foundations::Fields for #ident { impl #foundations::Fields for #ident {
type Enum = #enum_ident;
fn has(&self, id: u8) -> bool { fn has(&self, id: u8) -> bool {
let Ok(id) = <#ident as #foundations::ElementFields>::Fields::try_from(id) else { let Ok(id) = <#ident as #foundations::Fields>::Enum::try_from(id) else {
return false; return false;
}; };
@ -841,7 +767,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
} }
fn field(&self, id: u8) -> Option<#foundations::Value> { fn field(&self, id: u8) -> Option<#foundations::Value> {
let id = <#ident as #foundations::ElementFields>::Fields::try_from(id).ok()?; let id = <#ident as #foundations::Fields>::Enum::try_from(id).ok()?;
match id { match id {
#(#field_matches)* #(#field_matches)*
_ => None, _ => None,
@ -858,6 +784,75 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
} }
} }
/// 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)] });
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(()),
}
}
}
}
}
/// Creates the element's `Construct` implementation. /// Creates the element's `Construct` implementation.
fn create_construct_impl(element: &Elem) -> TokenStream { fn create_construct_impl(element: &Elem) -> TokenStream {
let ident = &element.ident; let ident = &element.ident;

View File

@ -159,12 +159,6 @@ cast! {
v: Func => v.element().ok_or("expected element")?, v: Func => v.element().ok_or("expected element")?,
} }
/// Fields of an element.
pub trait ElementFields {
/// The fields of the element.
type Fields;
}
/// A Typst element that is defined by a native Rust type. /// A Typst element that is defined by a native Rust type.
pub trait NativeElement: pub trait NativeElement:
Debug Debug
@ -215,6 +209,11 @@ pub unsafe trait Capable {
/// Defines how fields of an element are accessed. /// Defines how fields of an element are accessed.
pub trait Fields { pub trait Fields {
/// An enum with the fields of the element.
type Enum
where
Self: Sized;
/// Whether the element has the given field set. /// Whether the element has the given field set.
fn has(&self, id: u8) -> bool; fn has(&self, id: u8) -> bool;

View File

@ -26,7 +26,7 @@ macro_rules! __select_where {
let mut fields = ::smallvec::SmallVec::new(); let mut fields = ::smallvec::SmallVec::new();
$( $(
fields.push(( fields.push((
<$ty as $crate::foundations::ElementFields>::Fields::$field as u8, <$ty as $crate::foundations::Fields>::Enum::$field as u8,
$crate::foundations::IntoValue::into_value($value), $crate::foundations::IntoValue::into_value($value),
)); ));
)* )*