diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-01-16 10:24:36 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-16 09:24:36 +0000 |
| commit | c2dfbd39a024f277f7a18775590f135ff7da074f (patch) | |
| tree | 5da5ea13e282fbc77f47372c45265a314090e916 /crates/typst-macros | |
| parent | ad901c2cdbcb421355f5ecef5dc86c14de293c9a (diff) | |
Migrate metadata fields out of individual elements (#3200)
Diffstat (limited to 'crates/typst-macros')
| -rw-r--r-- | crates/typst-macros/src/elem.rs | 413 |
1 files changed, 85 insertions, 328 deletions
diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 2199ec92..6ab437cd 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -311,6 +311,8 @@ fn create(element: &Elem) -> Result<TokenStream> { settable.clone().map(|field| create_set_field_method(element, field)); // Trait implementations. + let fields_impl = create_fields_impl(element); + let capable_impl = create_capable_impl(element); let native_element_impl = create_native_elem_impl(element); let construct_impl = element.unless_capability("Construct", || create_construct_impl(element)); @@ -321,23 +323,11 @@ fn create(element: &Elem) -> Result<TokenStream> { element.unless_capability("PartialEq", || create_partial_eq_impl(element)); let repr_impl = element.unless_capability("Repr", || create_repr_impl(element)); - let label_and_location = element.unless_capability("Unlabellable", || { - quote! { - location: Option<::typst::introspection::Location>, - label: Option<#foundations::Label>, - prepared: bool, - } - }); - Ok(quote! { #[doc = #docs] #[derive(Debug, Clone, Hash)] #[allow(clippy::derived_hash_with_manual_eq)] #vis struct #ident { - span: ::typst::syntax::Span, - #label_and_location - guards: ::std::vec::Vec<#foundations::Guard>, - #(#fields,)* } @@ -353,6 +343,8 @@ fn create(element: &Elem) -> Result<TokenStream> { } #native_element_impl + #fields_impl + #capable_impl #construct_impl #set_impl #locatable_impl @@ -406,6 +398,8 @@ fn create_fields_enum(element: &Elem) -> TokenStream { quote! { #enum_ident } }); + let enum_repr = (!fields.is_empty()).then(|| quote! { #[repr(u8)] }); + quote! { // To hide the private type const _: () = { @@ -414,10 +408,9 @@ fn create_fields_enum(element: &Elem) -> TokenStream { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] - #[repr(u8)] + #enum_repr pub enum #enum_ident { #(#definitions,)* - Label = 255, } impl #enum_ident { @@ -425,7 +418,6 @@ fn create_fields_enum(element: &Elem) -> TokenStream { pub fn to_str(self) -> &'static str { match self { #(Self::#field_variants => #field_names,)* - Self::Label => "label", } } } @@ -437,7 +429,6 @@ fn create_fields_enum(element: &Elem) -> TokenStream { #(const #field_consts: u8 = #enum_ident::#field_variants as u8;)* match value { #(#field_consts => Ok(Self::#field_variants),)* - 255 => Ok(Self::Label), _ => Err(()), } } @@ -455,7 +446,6 @@ fn create_fields_enum(element: &Elem) -> TokenStream { fn from_str(s: &str) -> Result<Self, Self::Err> { match s { #(#field_names => Ok(Self::#field_variants),)* - "label" => Ok(Self::Label), _ => Err(()), } } @@ -495,21 +485,10 @@ fn create_new_func(element: &Elem) -> TokenStream { } }); - let label_and_location = element.unless_capability("Unlabellable", || { - quote! { - location: None, - label: None, - prepared: false, - } - }); - quote! { /// Create a new element. pub fn new(#(#params),*) -> Self { Self { - span: ::typst::syntax::Span::detached(), - #label_and_location - guards: ::std::vec::Vec::with_capacity(0), #(#required,)* #(#defaults,)* #(#default_synthesized,)* @@ -700,7 +679,6 @@ fn create_style_chain_access( fn create_native_elem_impl(element: &Elem) -> TokenStream { let Elem { name, ident, title, scope, keywords, docs, .. } = element; - let vtable_func = create_vtable_func(element); let params = element .fields .iter() @@ -713,30 +691,48 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { quote! { #foundations::Scope::new() } }; - // 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; + let local_name = element + .if_capability( + "LocalName", + || quote! { Some(<#foundations::Packed<#ident> as ::typst::text::LocalName>::local_name) }, + ) + .unwrap_or_else(|| quote! { None }); - if field.ghost { - quote! { - <#elem as #foundations::ElementFields>::Fields::#name => None, - } - } else if field.inherent() || (field.synthesized && field.default.is_some()) { - quote! { - <#elem as #foundations::ElementFields>::Fields::#name => Some( - #foundations::IntoValue::into_value(self.#field_ident.clone()) - ), - } - } else { - quote! { - <#elem as #foundations::ElementFields>::Fields::#name => { - self.#field_ident.clone().map(#foundations::IntoValue::into_value) - } + let data = quote! { + #foundations::NativeElementData { + name: #name, + title: #title, + docs: #docs, + keywords: &[#(#keywords),*], + construct: <#ident as #foundations::Construct>::construct, + 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), + 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), + local_name: #local_name, + scope: #foundations::Lazy::new(|| #scope), + params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]) + } + }; + + quote! { + impl #foundations::NativeElement for #ident { + fn data() -> &'static #foundations::NativeElementData { + static DATA: #foundations::NativeElementData = #data; + &DATA } } - }); + } +} + +fn create_fields_impl(element: &Elem) -> TokenStream { + let Elem { ident, .. } = element; // Fields that can be checked using the `has` method. let field_has_matches = element.visible_fields().map(|field| { @@ -759,80 +755,6 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { } }); - // Fields that can be set using the `set_field` method. - let field_set_matches = element - .visible_fields() - .filter(|field| field.settable() && !field.synthesized && !field.ghost) - .map(|field| { - let elem = &element.ident; - let name = &field.enum_ident; - let field_ident = &field.ident; - - quote! { - <#elem as #foundations::ElementFields>::Fields::#name => { - self.#field_ident = Some(#foundations::FromValue::from_value(value)?); - return Ok(()); - } - } - }); - - // Fields that are inherent. - let field_inherent_matches = element - .visible_fields() - .filter(|field| field.inherent() && !field.ghost) - .map(|field| { - let elem = &element.ident; - let name = &field.enum_ident; - let field_ident = &field.ident; - - quote! { - <#elem as #foundations::ElementFields>::Fields::#name => { - self.#field_ident = #foundations::FromValue::from_value(value)?; - return Ok(()); - } - } - }); - - // Fields that cannot be set or are internal create an error. - let field_not_set_matches = element - .real_fields() - .filter(|field| field.internal || field.synthesized || field.ghost) - .map(|field| { - let elem = &element.ident; - let ident = &field.enum_ident; - let field_name = &field.name; - if field.internal { - // Internal fields create an error that they are unknown. - let unknown_field = format!("unknown field `{field_name}` on `{name}`"); - quote! { - <#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field), - } - } else { - // Fields that cannot be set create an error that they are not settable. - let not_settable = format!("cannot set `{field_name}` on `{name}`"); - quote! { - <#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#not_settable), - } - } - }); - - // Statistically compute whether we need preparation or not. - let needs_preparation = element - .unless_capability("Unlabellable", || { - element - .capabilities - .iter() - .any(|capability| capability == "Locatable" || capability == "Synthesize") - .then(|| quote! { !self.prepared }) - .unwrap_or_else(|| quote! { self.label().is_some() && !self.prepared }) - }) - .unwrap_or_else(|| { - assert!(element.capabilities.iter().all(|capability| capability - != "Locatable" - && capability != "Synthesize")); - quote! { false } - }); - // Creation of the fields dictionary for inherent fields. let field_dict = element .inherent_fields() @@ -878,226 +800,58 @@ fn create_native_elem_impl(element: &Elem) -> TokenStream { } }); - let location = element - .unless_capability("Unlabellable", || quote! { self.location }) - .unwrap_or_else(|| quote! { None }); + // 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; - let set_location = element - .unless_capability("Unlabellable", || { + if field.ghost { quote! { - self.location = Some(location); + <#elem as #foundations::ElementFields>::Fields::#name => None, } - }) - .unwrap_or_else(|| quote! { drop(location) }); - - let label = element - .unless_capability("Unlabellable", || quote! { self.label }) - .unwrap_or_else(|| quote! { None }); - - let set_label = element - .unless_capability("Unlabellable", || { + } else if field.inherent() || (field.synthesized && field.default.is_some()) { quote! { - self.label = Some(label); + <#elem as #foundations::ElementFields>::Fields::#name => Some( + #foundations::IntoValue::into_value(self.#field_ident.clone()) + ), } - }) - .unwrap_or_else(|| quote! { drop(label) }); - - let label_field = element - .unless_capability("Unlabellable", || { + } else { quote! { - self.label().map(#foundations::Value::Label) - } - }) - .unwrap_or_else(|| quote! { None }); - - let label_has_field = element - .unless_capability("Unlabellable", || quote! { self.label().is_some() }) - .unwrap_or_else(|| quote! { false }); - - let label_field_dict = element.unless_capability("Unlabellable", || { - quote! { - if let Some(label) = self.label() { - fields.insert( - "label".into(), - #foundations::IntoValue::into_value(label) - ); + <#elem as #foundations::ElementFields>::Fields::#name => { + self.#field_ident.clone().map(#foundations::IntoValue::into_value) + } } } }); - let mark_prepared = element - .unless_capability("Unlabellable", || quote! { self.prepared = true; }) - .unwrap_or_else(|| quote! {}); - - let prepared = element - .unless_capability("Unlabellable", || quote! { self.prepared }) - .unwrap_or_else(|| quote! { true }); - - let local_name = element - .if_capability( - "LocalName", - || quote! { Some(<#ident as ::typst::text::LocalName>::local_name) }, - ) - .unwrap_or_else(|| quote! { None }); - - let unknown_field = format!("unknown field {{}} on {name}"); - let label_error = format!("cannot set label on {name}"); - let data = quote! { - #foundations::NativeElementData { - name: #name, - title: #title, - docs: #docs, - keywords: &[#(#keywords),*], - construct: <#ident as #foundations::Construct>::construct, - set: <#ident as #foundations::Set>::set, - vtable: #vtable_func, - field_id: |name| - < - <#ident as #foundations::ElementFields>::Fields 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), - local_name: #local_name, - scope: #foundations::Lazy::new(|| #scope), - params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]) - } - }; - quote! { - impl #foundations::NativeElement for #ident { - fn data() -> &'static #foundations::NativeElementData { - static DATA: #foundations::NativeElementData = #data; - &DATA - } - - fn dyn_elem(&self) -> #foundations::Element { - #foundations::Element::of::<Self>() - } - - fn dyn_hash(&self, mut state: &mut dyn ::std::hash::Hasher) { - // Also hash the TypeId since values with different types but - // equal data should be different. - ::std::hash::Hash::hash(&::std::any::TypeId::of::<Self>(), &mut state); - ::std::hash::Hash::hash(self, &mut state); - } - - fn dyn_eq(&self, other: &#foundations::Content) -> bool { - if let Some(other) = other.to::<Self>() { - <Self as ::std::cmp::PartialEq>::eq(self, other) - } else { - false - } - } - - fn dyn_clone(&self) -> ::std::sync::Arc<dyn #foundations::NativeElement> { - ::std::sync::Arc::new(Clone::clone(self)) - } - - fn as_any(&self) -> &dyn ::std::any::Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { - self - } - - fn into_any(self: ::std::sync::Arc<Self>) -> ::std::sync::Arc<dyn ::std::any::Any + Send + Sync> { - self - } - - fn span(&self) -> ::typst::syntax::Span { - self.span - } + impl #foundations::Fields for #ident { + fn has(&self, id: u8) -> bool { + let Ok(id) = <#ident as #foundations::ElementFields>::Fields::try_from(id) else { + return false; + }; - fn set_span(&mut self, span: ::typst::syntax::Span) { - if self.span().is_detached() { - self.span = span; + match id { + #(#field_has_matches)* + _ => false, } } - fn label(&self) -> Option<#foundations::Label> { - #label - } - - fn set_label(&mut self, label: #foundations::Label) { - #set_label - } - - fn location(&self) -> Option<::typst::introspection::Location> { - #location - } - - fn set_location(&mut self, location: ::typst::introspection::Location) { - #set_location - } - - fn push_guard(&mut self, guard: #foundations::Guard) { - self.guards.push(guard); - } - - fn is_guarded(&self, guard: #foundations::Guard) -> bool { - self.guards.contains(&guard) - } - - fn is_pristine(&self) -> bool { - self.guards.is_empty() - } - - fn mark_prepared(&mut self) { - #mark_prepared - } - - fn needs_preparation(&self) -> bool { - #needs_preparation - } - - fn is_prepared(&self) -> bool { - #prepared - } - fn field(&self, id: u8) -> Option<#foundations::Value> { let id = <#ident as #foundations::ElementFields>::Fields::try_from(id).ok()?; match id { - <#ident as #foundations::ElementFields>::Fields::Label => #label_field, #(#field_matches)* _ => None, } } - fn has(&self, id: u8) -> bool { - let Ok(id) = <#ident as #foundations::ElementFields>::Fields::try_from(id) else { - return false; - }; - - match id { - <#ident as #foundations::ElementFields>::Fields::Label => #label_has_field, - #(#field_has_matches)* - _ => false, - } - } - fn fields(&self) -> #foundations::Dict { let mut fields = #foundations::Dict::new(); - #label_field_dict #(#field_dict)* #(#field_opt_dict)* fields } - - fn set_field(&mut self, id: u8, value: #foundations::Value) -> ::typst::diag::StrResult<()> { - let id = <#ident as #foundations::ElementFields>::Fields::try_from(id) - .map_err(|_| ::ecow::eco_format!(#unknown_field, id))?; - match id { - #(#field_set_matches)* - #(#field_inherent_matches)* - #(#field_not_set_matches)* - <#ident as #foundations::ElementFields>::Fields::Label => { - ::typst::diag::bail!(#label_error); - } - } - } } } } @@ -1182,7 +936,7 @@ 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 #ident {} } + quote! { impl ::typst::introspection::Locatable for #foundations::Packed<#ident> {} } } /// Creates the element's `PartialEq` implementation. @@ -1208,7 +962,7 @@ fn create_repr_impl(element: &Elem) -> TokenStream { quote! { impl #foundations::Repr for #ident { fn repr(&self) -> ::ecow::EcoString { - let fields = #foundations::NativeElement::fields(self).into_iter() + 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)) @@ -1218,9 +972,9 @@ fn create_repr_impl(element: &Elem) -> TokenStream { } /// Creates the element's casting vtable. -fn create_vtable_func(element: &Elem) -> TokenStream { +fn create_capable_impl(element: &Elem) -> TokenStream { // Forbidden capabilities (i.e capabilities that are not object safe). - const FORBIDDEN: &[&str] = &["Construct", "PartialEq", "Hash", "LocalName"]; + const FORBIDDEN: &[&str] = &["Construct", "PartialEq", "Hash", "LocalName", "Repr"]; let ident = &element.ident; let relevant = element @@ -1229,20 +983,23 @@ fn create_vtable_func(element: &Elem) -> TokenStream { .filter(|&ident| !FORBIDDEN.contains(&(&ident.to_string() as &str))); let checks = relevant.map(|capability| { quote! { - if id == ::std::any::TypeId::of::<dyn #capability>() { - let vtable = unsafe { - let dangling = ::std::ptr::NonNull::<#ident>::dangling().as_ptr() as *const dyn #capability; - ::typst::util::fat::vtable(dangling) - }; - return Some(vtable); + if capability == ::std::any::TypeId::of::<dyn #capability>() { + // Safety: The vtable function doesn't require initialized + // data, so it's fine to use a dangling pointer. + return Some(unsafe { + ::typst::util::fat::vtable(dangling as *const dyn #capability) + }); } } }); quote! { - |id| { - #(#checks)* - None + unsafe impl #foundations::Capable for #ident { + fn vtable(capability: ::std::any::TypeId) -> ::std::option::Option<*const ()> { + let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr(); + #(#checks)* + None + } } } } |
