From c2dfbd39a024f277f7a18775590f135ff7da074f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 16 Jan 2024 10:24:36 +0100 Subject: [PATCH] Migrate metadata fields out of individual elements (#3200) --- crates/typst-macros/src/elem.rs | 415 ++++--------------- crates/typst-pdf/src/outline.rs | 26 +- crates/typst/src/foundations/cast.rs | 35 +- crates/typst/src/foundations/content.rs | 456 +++++++++++++++------ crates/typst/src/foundations/element.rs | 136 ++---- crates/typst/src/foundations/styles.rs | 7 +- crates/typst/src/introspection/counter.rs | 16 +- crates/typst/src/introspection/locate.rs | 8 +- crates/typst/src/introspection/metadata.rs | 8 +- crates/typst/src/introspection/mod.rs | 5 +- crates/typst/src/introspection/state.rs | 12 +- crates/typst/src/layout/align.rs | 6 +- crates/typst/src/layout/columns.rs | 6 +- crates/typst/src/layout/container.rs | 6 +- crates/typst/src/layout/flow.rs | 18 +- crates/typst/src/layout/grid/mod.rs | 16 +- crates/typst/src/layout/hide.rs | 4 +- crates/typst/src/layout/inline/mod.rs | 8 +- crates/typst/src/layout/layout.rs | 8 +- crates/typst/src/layout/pad.rs | 4 +- crates/typst/src/layout/page.rs | 10 +- crates/typst/src/layout/place.rs | 8 +- crates/typst/src/layout/repeat.rs | 4 +- crates/typst/src/layout/spacing.rs | 14 +- crates/typst/src/layout/stack.rs | 4 +- crates/typst/src/layout/transform.rs | 8 +- crates/typst/src/math/accent.rs | 4 +- crates/typst/src/math/align.rs | 4 +- crates/typst/src/math/attach.rs | 22 +- crates/typst/src/math/cancel.rs | 4 +- crates/typst/src/math/class.rs | 4 +- crates/typst/src/math/ctx.rs | 8 +- crates/typst/src/math/equation.rs | 59 ++- crates/typst/src/math/frac.rs | 6 +- crates/typst/src/math/lr.rs | 11 +- crates/typst/src/math/matrix.rs | 10 +- crates/typst/src/math/op.rs | 4 +- crates/typst/src/math/root.rs | 6 +- crates/typst/src/math/style.rs | 32 +- crates/typst/src/math/underover.rs | 14 +- crates/typst/src/model/bibliography.rs | 51 +-- crates/typst/src/model/cite.rs | 21 +- crates/typst/src/model/document.rs | 5 +- crates/typst/src/model/emph.rs | 4 +- crates/typst/src/model/enum.rs | 13 +- crates/typst/src/model/figure.rs | 98 +++-- crates/typst/src/model/footnote.rs | 32 +- crates/typst/src/model/heading.rs | 67 ++- crates/typst/src/model/link.rs | 4 +- crates/typst/src/model/list.rs | 15 +- crates/typst/src/model/outline.rs | 18 +- crates/typst/src/model/par.rs | 6 +- crates/typst/src/model/quote.rs | 21 +- crates/typst/src/model/reference.rs | 56 +-- crates/typst/src/model/strong.rs | 4 +- crates/typst/src/model/table.rs | 18 +- crates/typst/src/model/terms.rs | 8 +- crates/typst/src/realize/mod.rs | 57 +-- crates/typst/src/text/deco.rs | 12 +- crates/typst/src/text/linebreak.rs | 4 +- crates/typst/src/text/mod.rs | 3 +- crates/typst/src/text/raw.rs | 58 +-- crates/typst/src/text/shift.rs | 6 +- crates/typst/src/text/space.rs | 10 +- crates/typst/src/visualize/image/mod.rs | 12 +- crates/typst/src/visualize/line.rs | 4 +- crates/typst/src/visualize/path.rs | 4 +- crates/typst/src/visualize/polygon.rs | 8 +- crates/typst/src/visualize/shape.rs | 10 +- 69 files changed, 1014 insertions(+), 1051 deletions(-) diff --git a/crates/typst-macros/src/elem.rs b/crates/typst-macros/src/elem.rs index 2199ec92c..6ab437cd3 100644 --- a/crates/typst-macros/src/elem.rs +++ b/crates/typst-macros/src/elem.rs @@ -311,6 +311,8 @@ fn create(element: &Elem) -> Result { 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 { 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 { } #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 { 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 + >::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 - >::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::() - } - - 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::(), &mut state); - ::std::hash::Hash::hash(self, &mut state); - } - - fn dyn_eq(&self, other: &#foundations::Content) -> bool { - if let Some(other) = other.to::() { - ::eq(self, other) - } else { - false - } - } - - fn dyn_clone(&self) -> ::std::sync::Arc { - ::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) -> ::std::sync::Arc { - self - } - - fn span(&self) -> ::typst::syntax::Span { - self.span - } - - fn set_span(&mut self, span: ::typst::syntax::Span) { - if self.span().is_detached() { - self.span = span; - } - } - - 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, - } - } - + 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; }; match id { - <#ident as #foundations::ElementFields>::Fields::Label => #label_has_field, #(#field_has_matches)* _ => false, } } + fn field(&self, id: u8) -> Option<#foundations::Value> { + let id = <#ident as #foundations::ElementFields>::Fields::try_from(id).ok()?; + match id { + #(#field_matches)* + _ => None, + } + } + 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::>(); ::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::() { - 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::() { + // 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 + } } } } diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs index 78698cf8c..172f6d3cc 100644 --- a/crates/typst-pdf/src/outline.rs +++ b/crates/typst-pdf/src/outline.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use pdf_writer::{Finish, Ref, TextStr}; -use typst::foundations::{Content, NativeElement, Smart}; +use typst::foundations::{NativeElement, Packed, StyleChain}; use typst::layout::Abs; use typst::model::HeadingElem; @@ -17,8 +17,10 @@ pub(crate) fn write_outline(ctx: &mut PdfContext) -> Option { // Therefore, its next descendant must be added at its level, which is // enforced in the manner shown below. let mut last_skipped_level = None; - for heading in ctx.document.introspector.query(&HeadingElem::elem().select()).iter() { - let leaf = HeadingNode::leaf((**heading).clone()); + let elements = ctx.document.introspector.query(&HeadingElem::elem().select()); + for elem in elements.iter() { + let heading = elem.to::().unwrap(); + let leaf = HeadingNode::leaf(heading); if leaf.bookmarked { let mut children = &mut tree; @@ -103,21 +105,21 @@ pub(crate) fn write_outline(ctx: &mut PdfContext) -> Option { /// A heading in the outline panel. #[derive(Debug, Clone)] -struct HeadingNode { - element: Content, +struct HeadingNode<'a> { + element: &'a Packed, level: NonZeroUsize, bookmarked: bool, - children: Vec, + children: Vec>, } -impl HeadingNode { - fn leaf(element: Content) -> Self { +impl<'a> HeadingNode<'a> { + fn leaf(element: &'a Packed) -> Self { HeadingNode { - level: element.expect_field_by_name::("level"), + level: element.level(StyleChain::default()), // 'bookmarked' set to 'auto' falls back to the value of 'outlined'. bookmarked: element - .expect_field_by_name::>("bookmarked") - .unwrap_or_else(|| element.expect_field_by_name::("outlined")), + .bookmarked(StyleChain::default()) + .unwrap_or_else(|| element.outlined(StyleChain::default())), element, children: Vec::new(), } @@ -157,7 +159,7 @@ fn write_outline_item( outline.count(-(node.children.len() as i32)); } - let body = node.element.expect_field_by_name::("body"); + let body = node.element.body(); outline.title(TextStr(body.plain_text().trim())); let loc = node.element.location().unwrap(); diff --git a/crates/typst/src/foundations/cast.rs b/crates/typst/src/foundations/cast.rs index e3b7dd014..2f7e1dad4 100644 --- a/crates/typst/src/foundations/cast.rs +++ b/crates/typst/src/foundations/cast.rs @@ -9,7 +9,7 @@ use smallvec::SmallVec; use unicode_math_class::MathClass; use crate::diag::{At, SourceResult, StrResult}; -use crate::foundations::{repr, Repr, Type, Value}; +use crate::foundations::{repr, NativeElement, Packed, Repr, Type, Value}; use crate::syntax::{Span, Spanned}; #[rustfmt::skip] @@ -84,6 +84,20 @@ impl Reflect for Spanned { } } +impl Reflect for Packed { + fn input() -> CastInfo { + T::input() + } + + fn output() -> CastInfo { + T::output() + } + + fn castable(value: &Value) -> bool { + T::castable(value) + } +} + impl Reflect for Prehashed { fn input() -> CastInfo { T::input() @@ -174,6 +188,12 @@ impl IntoValue for Cow<'_, T> { } } +impl IntoValue for Packed { + fn into_value(self) -> Value { + Value::Content(self.pack()) + } +} + impl IntoValue for Spanned { fn into_value(self) -> Value { self.v.into_value() @@ -233,6 +253,19 @@ impl FromValue for Value { } } +impl FromValue for Packed { + fn from_value(mut value: Value) -> StrResult { + if let Value::Content(content) = value { + match content.to_packed::() { + Ok(packed) => return Ok(packed), + Err(content) => value = Value::Content(content), + } + } + let val = T::from_value(value)?; + Ok(Packed::new(val)) + } +} + impl FromValue for Prehashed { fn from_value(value: Value) -> StrResult { Ok(Self::new(T::from_value(value)?)) diff --git a/crates/typst/src/foundations/content.rs b/crates/typst/src/foundations/content.rs index a0cb45cfc..3ba458afd 100644 --- a/crates/typst/src/foundations/content.rs +++ b/crates/typst/src/foundations/content.rs @@ -1,7 +1,9 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; -use std::ops::{Add, AddAssign}; +use std::marker::PhantomData; +use std::ops::{Add, AddAssign, Deref, DerefMut}; use std::sync::Arc; use comemo::Prehashed; @@ -12,10 +14,10 @@ use smallvec::smallvec; use crate::diag::{SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ - elem, func, scope, ty, Dict, Element, FromValue, Guard, IntoValue, Label, - NativeElement, Recipe, Repr, Selector, Str, Style, Styles, Value, + elem, func, scope, ty, Dict, Element, Fields, Guard, IntoValue, Label, NativeElement, + Recipe, Repr, Selector, Str, Style, Styles, Synthesize, Value, }; -use crate::introspection::{Location, Meta, MetaElem}; +use crate::introspection::{Locatable, Location, Meta, MetaElem}; use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; use crate::model::{Destination, EmphElem, StrongElem}; use crate::syntax::Span; @@ -68,12 +70,34 @@ use crate::util::fat; #[ty(scope, cast)] #[derive(Clone, Hash)] #[allow(clippy::derived_hash_with_manual_eq)] -pub struct Content(Arc); +pub struct Content { + inner: Arc>, + span: Span, +} + +/// The inner representation behind the `Arc`. +#[derive(Hash)] +struct Inner { + label: Option