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 {
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 {
name,
@ -285,22 +291,14 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
fn create(element: &Elem) -> Result<TokenStream> {
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 present = element.present_fields();
let settable = all.clone().filter(|field| !field.synthesized && field.settable());
// The struct itself.
let fields = present.clone().map(create_field);
let fields_enum = create_fields_enum(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 =
@ -333,31 +331,31 @@ fn create(element: &Elem) -> Result<TokenStream> {
#(#fields,)*
}
#fields_enum
impl #ident {
#new
#(#field_methods)*
#(#field_in_methods)*
#(#with_field_methods)*
#(#push_field_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))
const _: () = {
impl #ident {
#new
#(#field_methods)*
#(#field_in_methods)*
#(#with_field_methods)*
#(#push_field_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))
}
}
};
})
}
@ -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.
fn create_new_func(element: &Elem) -> TokenStream {
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 {
#foundations::Style::Property(#foundations::Property::new(
<Self as #foundations::NativeElement>::elem(),
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8,
<#elem as #foundations::Fields>::Enum::#enum_ident as u8,
#ident,
))
}
@ -670,7 +592,7 @@ fn create_style_chain_access(
#init
styles.#getter::<#ty>(
<Self as #foundations::NativeElement>::elem(),
<#elem as #foundations::ElementFields>::Fields::#enum_ident as u8,
<#elem as #foundations::Fields>::Enum::#enum_ident as u8,
#inherent,
#default,
)
@ -710,13 +632,11 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream {
set: <#ident as #foundations::Set>::set,
vtable: <#ident as #foundations::Capable>::vtable,
field_id: |name|
<
<#ident as #foundations::ElementFields>::Fields as ::std::str::FromStr
>::from_str(name).ok().map(|id| id as u8),
<<#ident as #foundations::Fields>::Enum as ::std::str::FromStr>
::from_str(name).ok().map(|id| id as u8),
field_name: |id|
<
<#ident as #foundations::ElementFields>::Fields as ::std::convert::TryFrom<u8>
>::try_from(id).ok().map(<#ident as #foundations::ElementFields>::Fields::to_str),
<<#ident as #foundations::Fields>::Enum as ::std::convert::TryFrom<u8>>
::try_from(id).ok().map(<#ident as #foundations::Fields>::Enum::to_str),
local_name: #local_name,
scope: #foundations::Lazy::new(|| #scope),
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 {
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.
let field_has_matches = element.visible_fields().map(|field| {
@ -744,15 +666,15 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
if field.ghost {
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()) {
quote! {
<#elem as #foundations::ElementFields>::Fields::#name => true,
<#elem as #foundations::Fields>::Enum::#name => true,
}
} else {
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 {
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()) {
quote! {
<#elem as #foundations::ElementFields>::Fields::#name => Some(
<#elem as #foundations::Fields>::Enum::#name => Some(
#foundations::IntoValue::into_value(self.#field_ident.clone())
),
}
} else {
quote! {
<#elem as #foundations::ElementFields>::Fields::#name => {
<#elem as #foundations::Fields>::Enum::#name => {
self.#field_ident.clone().map(#foundations::IntoValue::into_value)
}
}
@ -828,9 +750,13 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
});
quote! {
#fields_enum
impl #foundations::Fields for #ident {
type Enum = #enum_ident;
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;
};
@ -841,7 +767,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
}
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 {
#(#field_matches)*
_ => 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.
fn create_construct_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;

View File

@ -159,12 +159,6 @@ cast! {
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.
pub trait NativeElement:
Debug
@ -215,6 +209,11 @@ pub unsafe trait Capable {
/// Defines how fields of an element are accessed.
pub trait Fields {
/// An enum with the fields of the element.
type Enum
where
Self: Sized;
/// Whether the element has the given field set.
fn has(&self, id: u8) -> bool;

View File

@ -26,7 +26,7 @@ macro_rules! __select_where {
let mut fields = ::smallvec::SmallVec::new();
$(
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),
));
)*