diff options
| author | Laurenz <laurmaedje@gmail.com> | 2025-07-08 10:52:43 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-08 08:52:43 +0000 |
| commit | 0a3c6939dd274f40672484695d909c2cc0d0d755 (patch) | |
| tree | 465c10338230b895fdd06c8b3491f1734e8a2932 /crates/typst-library/src/foundations | |
| parent | 36ecbb2c8dccc1a31c43fee1466f1425844d8607 (diff) | |
Rewrite foundations of native elements (#6547)
Diffstat (limited to 'crates/typst-library/src/foundations')
| -rw-r--r-- | crates/typst-library/src/foundations/content/element.rs (renamed from crates/typst-library/src/foundations/element.rs) | 216 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/content/field.rs | 564 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/content/mod.rs (renamed from crates/typst-library/src/foundations/content.rs) | 434 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/content/packed.rs | 147 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/content/raw.rs | 426 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/content/vtable.rs | 383 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/mod.rs | 2 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/scope.rs | 11 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/selector.rs | 4 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/styles.rs | 222 | ||||
| -rw-r--r-- | crates/typst-library/src/foundations/target.rs | 2 |
11 files changed, 1838 insertions, 573 deletions
diff --git a/crates/typst-library/src/foundations/element.rs b/crates/typst-library/src/foundations/content/element.rs index 7ff00b9d..49b0b0f9 100644 --- a/crates/typst-library/src/foundations/element.rs +++ b/crates/typst-library/src/foundations/content/element.rs @@ -2,52 +2,54 @@ use std::any::TypeId; use std::cmp::Ordering; use std::fmt::{self, Debug}; use std::hash::Hash; -use std::ptr::NonNull; -use std::sync::LazyLock; +use std::sync::OnceLock; use ecow::EcoString; use smallvec::SmallVec; -#[doc(inline)] -pub use typst_macros::elem; use typst_utils::Static; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{ - cast, Args, Content, Dict, FieldAccessError, Func, ParamInfo, Repr, Scope, Selector, - StyleChain, Styles, Value, + cast, Args, Content, ContentVtable, FieldAccessError, Func, ParamInfo, Repr, Scope, + Selector, StyleChain, Styles, Value, }; use crate::text::{Lang, Region}; /// A document element. #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Element(Static<NativeElementData>); +pub struct Element(Static<ContentVtable>); impl Element { /// Get the element for `T`. - pub fn of<T: NativeElement>() -> Self { - T::elem() + pub const fn of<T: NativeElement>() -> Self { + T::ELEM + } + + /// Get the element for `T`. + pub const fn from_vtable(vtable: &'static ContentVtable) -> Self { + Self(Static(vtable)) } /// The element's normal name (e.g. `enum`). pub fn name(self) -> &'static str { - self.0.name + self.vtable().name } /// The element's title case name, for use in documentation /// (e.g. `Numbered List`). pub fn title(&self) -> &'static str { - self.0.title + self.vtable().title } /// Documentation for the element (as Markdown). pub fn docs(&self) -> &'static str { - self.0.docs + self.vtable().docs } /// Search keywords for the element. pub fn keywords(&self) -> &'static [&'static str] { - self.0.keywords + self.vtable().keywords } /// Construct an instance of this element. @@ -56,12 +58,12 @@ impl Element { engine: &mut Engine, args: &mut Args, ) -> SourceResult<Content> { - (self.0.construct)(engine, args) + (self.vtable().construct)(engine, args) } /// Execute the set rule for the element and return the resulting style map. pub fn set(self, engine: &mut Engine, mut args: Args) -> SourceResult<Styles> { - let styles = (self.0.set)(engine, &mut args)?; + let styles = (self.vtable().set)(engine, &mut args)?; args.finish()?; Ok(styles) } @@ -77,12 +79,7 @@ impl Element { /// Whether the element has the given capability where the capability is /// given by a `TypeId`. pub fn can_type_id(self, type_id: TypeId) -> bool { - (self.0.vtable)(type_id).is_some() - } - - /// The VTable for capabilities dispatch. - pub fn vtable(self) -> fn(of: TypeId) -> Option<NonNull<()>> { - self.0.vtable + (self.vtable().capability)(type_id).is_some() } /// Create a selector for this element. @@ -98,12 +95,29 @@ impl Element { /// The element's associated scope of sub-definition. pub fn scope(&self) -> &'static Scope { - &(self.0).0.scope + (self.vtable().store)().scope.get_or_init(|| (self.vtable().scope)()) } /// Details about the element's fields. pub fn params(&self) -> &'static [ParamInfo] { - &(self.0).0.params + (self.vtable().store)().params.get_or_init(|| { + self.vtable() + .fields + .iter() + .filter(|field| !field.synthesized) + .map(|field| ParamInfo { + name: field.name, + docs: field.docs, + input: (field.input)(), + default: field.default, + positional: field.positional, + named: !field.positional, + variadic: field.variadic, + required: field.required, + settable: field.settable, + }) + .collect() + }) } /// Extract the field ID for the given field name. @@ -111,7 +125,7 @@ impl Element { if name == "label" { return Some(255); } - (self.0.field_id)(name) + (self.vtable().field_id)(name) } /// Extract the field name for the given field ID. @@ -119,7 +133,7 @@ impl Element { if id == 255 { return Some("label"); } - (self.0.field_name)(id) + self.vtable().field(id).map(|data| data.name) } /// Extract the value of the field for the given field ID and style chain. @@ -128,12 +142,20 @@ impl Element { id: u8, styles: StyleChain, ) -> Result<Value, FieldAccessError> { - (self.0.field_from_styles)(id, styles) + self.vtable() + .field(id) + .and_then(|field| (field.get_from_styles)(styles)) + .ok_or(FieldAccessError::Unknown) } /// The element's local name, if any. pub fn local_name(&self, lang: Lang, region: Option<Region>) -> Option<&'static str> { - (self.0).0.local_name.map(|f| f(lang, region)) + self.vtable().local_name.map(|f| f(lang, region)) + } + + /// Retrieves the element's vtable for dynamic dispatch. + pub(super) fn vtable(&self) -> &'static ContentVtable { + (self.0).0 } } @@ -167,84 +189,34 @@ cast! { v: Func => v.element().ok_or("expected element")?, } -/// A Typst element that is defined by a native Rust type. -pub trait NativeElement: - Debug - + Clone - + PartialEq - + Hash - + Construct - + Set - + Capable - + Fields - + Repr - + Send - + Sync - + 'static -{ - /// Get the element for the native Rust element. - fn elem() -> Element - where - Self: Sized, - { - Element::from(Self::data()) - } +/// Lazily initialized data for an element. +#[derive(Default)] +pub struct LazyElementStore { + pub scope: OnceLock<Scope>, + pub params: OnceLock<Vec<ParamInfo>>, +} - /// Pack the element into type-erased content. - fn pack(self) -> Content - where - Self: Sized, - { - Content::new(self) +impl LazyElementStore { + /// Create an empty store. + pub const fn new() -> Self { + Self { scope: OnceLock::new(), params: OnceLock::new() } } - - /// Get the element data for the native Rust element. - fn data() -> &'static NativeElementData - where - Self: Sized; } -/// Used to cast an element to a trait object for a trait it implements. +/// A Typst element that is defined by a native Rust type. /// /// # Safety -/// If the `vtable` function returns `Some(p)`, then `p` must be a valid pointer -/// to a vtable of `Packed<Self>` w.r.t to the trait `C` where `capability` is -/// `TypeId::of::<dyn C>()`. -pub unsafe trait Capable { - /// Get the pointer to the vtable for the given capability / trait. - fn vtable(capability: TypeId) -> Option<NonNull<()>>; -} - -/// 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; - - /// Get the field with the given field ID. - fn field(&self, id: u8) -> Result<Value, FieldAccessError>; - - /// Get the field with the given ID in the presence of styles. - fn field_with_styles( - &self, - id: u8, - styles: StyleChain, - ) -> Result<Value, FieldAccessError>; - - /// Get the field with the given ID from the styles. - fn field_from_styles(id: u8, styles: StyleChain) -> Result<Value, FieldAccessError> - where - Self: Sized; - - /// Resolve all fields with the styles and save them in-place. - fn materialize(&mut self, styles: StyleChain); +/// `ELEM` must hold the correct `Element` for `Self`. +pub unsafe trait NativeElement: + Debug + Clone + Hash + Construct + Set + Send + Sync + 'static +{ + /// The associated element. + const ELEM: Element; - /// Get the fields of the element. - fn fields(&self) -> Dict; + /// Pack the element into type-erased content. + fn pack(self) -> Content { + Content::new(self) + } } /// An element's constructor function. @@ -266,48 +238,6 @@ pub trait Set { Self: Sized; } -/// Defines a native element. -#[derive(Debug)] -pub struct NativeElementData { - /// The element's normal name (e.g. `align`), as exposed to Typst. - pub name: &'static str, - /// The element's title case name (e.g. `Align`). - pub title: &'static str, - /// The documentation for this element as a string. - pub docs: &'static str, - /// A list of alternate search terms for this element. - pub keywords: &'static [&'static str], - /// The constructor for this element (see [`Construct`]). - pub construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>, - /// Executes this element's set rule (see [`Set`]). - pub set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>, - /// Gets the vtable for one of this element's capabilities - /// (see [`Capable`]). - pub vtable: fn(capability: TypeId) -> Option<NonNull<()>>, - /// Gets the numeric index of this field by its name. - pub field_id: fn(name: &str) -> Option<u8>, - /// Gets the name of a field by its numeric index. - pub field_name: fn(u8) -> Option<&'static str>, - /// Get the field with the given ID in the presence of styles (see [`Fields`]). - pub field_from_styles: fn(u8, StyleChain) -> Result<Value, FieldAccessError>, - /// Gets the localized name for this element (see [`LocalName`][crate::text::LocalName]). - pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>, - pub scope: LazyLock<Scope>, - /// A list of parameter information for each field. - pub params: LazyLock<Vec<ParamInfo>>, -} - -impl From<&'static NativeElementData> for Element { - fn from(data: &'static NativeElementData) -> Self { - Self(Static(data)) - } -} - -cast! { - &'static NativeElementData, - self => Element::from(self).into_value(), -} - /// Synthesize fields on an element. This happens before execution of any show /// rule. pub trait Synthesize { @@ -331,3 +261,9 @@ pub trait ShowSet { /// that should work even in the face of a user-defined show rule. fn show_set(&self, styles: StyleChain) -> Styles; } + +/// Tries to extract the plain-text representation of the element. +pub trait PlainText { + /// Write this element's plain text into the given buffer. + fn plain_text(&self, text: &mut EcoString); +} diff --git a/crates/typst-library/src/foundations/content/field.rs b/crates/typst-library/src/foundations/content/field.rs new file mode 100644 index 00000000..8d0fe529 --- /dev/null +++ b/crates/typst-library/src/foundations/content/field.rs @@ -0,0 +1,564 @@ +use std::fmt::{self, Debug}; +use std::hash::Hash; +use std::marker::PhantomData; +use std::sync::OnceLock; + +use ecow::{eco_format, EcoString}; + +use crate::foundations::{ + Container, Content, FieldVtable, Fold, FoldFn, IntoValue, NativeElement, Packed, + Property, Reflect, Repr, Resolve, StyleChain, +}; + +/// An accessor for the `I`-th field of the element `E`. Values of this type are +/// generated for each field of an element can be used to interact with this +/// field programmatically, for example to access the style chain, as in +/// `styles.get(TextElem::size)`. +#[derive(Copy, Clone)] +pub struct Field<E: NativeElement, const I: u8>(pub PhantomData<E>); + +impl<E: NativeElement, const I: u8> Field<E, I> { + /// Creates a new zero-sized accessor. + pub const fn new() -> Self { + Self(PhantomData) + } + + /// The index of the projected field. + pub const fn index(self) -> u8 { + I + } + + /// Creates a dynamic property instance for this field. + /// + /// Prefer [`Content::set`] or + /// [`Styles::set`](crate::foundations::Styles::set) when working with + /// existing content or style value. + pub fn set(self, value: E::Type) -> Property + where + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, + { + Property::new(self, value) + } +} + +impl<E: NativeElement, const I: u8> Default for Field<E, I> { + fn default() -> Self { + Self::new() + } +} + +/// A field that is present on every instance of the element. +pub trait RequiredField<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: RequiredFieldData<Self, I>; +} + +/// Metadata and routines for a [`RequiredField`]. +pub struct RequiredFieldData<E: RequiredField<I>, const I: u8> { + name: &'static str, + docs: &'static str, + get: fn(&E) -> &E::Type, +} + +impl<E: RequiredField<I>, const I: u8> RequiredFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + get: fn(&E) -> &E::Type, + ) -> Self { + Self { name, docs, get } + } + + /// Creates the vtable for a `#[required]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: RequiredField<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: true, + required: true, + variadic: false, + settable: false, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: None, + has: |_| true, + get: |elem| Some((E::FIELD.get)(elem).clone().into_value()), + get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()), + get_from_styles: |_| None, + materialize: |_, _| {}, + eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b), + } + } + + /// Creates the vtable for a `#[variadic]` field. + pub const fn vtable_variadic() -> FieldVtable<Packed<E>> + where + E: RequiredField<I>, + E::Type: Container + IntoValue + PartialEq, + <E::Type as Container>::Inner: Reflect, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: true, + required: true, + variadic: true, + settable: false, + synthesized: false, + input: || <<E::Type as Container>::Inner as Reflect>::input(), + default: None, + has: |_| true, + get: |elem| Some((E::FIELD.get)(elem).clone().into_value()), + get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()), + get_from_styles: |_| None, + materialize: |_, _| {}, + eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b), + } + } +} + +/// A field that is initially unset, but may be set through a +/// [`Synthesize`](crate::foundations::Synthesize) implementation. +pub trait SynthesizedField<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: SynthesizedFieldData<Self, I>; +} + +/// Metadata and routines for a [`SynthesizedField`]. +pub struct SynthesizedFieldData<E: SynthesizedField<I>, const I: u8> { + name: &'static str, + docs: &'static str, + get: fn(&E) -> &Option<E::Type>, +} + +impl<E: SynthesizedField<I>, const I: u8> SynthesizedFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + get: fn(&E) -> &Option<E::Type>, + ) -> Self { + Self { name, docs, get } + } + + /// Creates type-erased metadata and routines for a `#[synthesized]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: SynthesizedField<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: false, + required: false, + variadic: false, + settable: false, + synthesized: true, + input: || <E::Type as Reflect>::input(), + default: None, + has: |elem| (E::FIELD.get)(elem).is_some(), + get: |elem| (E::FIELD.get)(elem).clone().map(|v| v.into_value()), + get_with_styles: |elem, _| { + (E::FIELD.get)(elem).clone().map(|v| v.into_value()) + }, + get_from_styles: |_| None, + materialize: |_, _| {}, + // Synthesized fields don't affect equality. + eq: |_, _| true, + } + } +} + +/// A field that is not actually there. It's only visible in the docs. +pub trait ExternalField<const I: u8>: NativeElement { + type Type; + + const FIELD: ExternalFieldData<Self, I>; +} + +/// Metadata for an [`ExternalField`]. +pub struct ExternalFieldData<E: ExternalField<I>, const I: u8> { + name: &'static str, + docs: &'static str, + default: fn() -> E::Type, +} + +impl<E: ExternalField<I>, const I: u8> ExternalFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + default: fn() -> E::Type, + ) -> Self { + Self { name, docs, default } + } + + /// Creates type-erased metadata and routines for an `#[external]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: ExternalField<I>, + E::Type: Reflect + IntoValue, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: false, + required: false, + variadic: false, + settable: false, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: Some(|| (E::FIELD.default)().into_value()), + has: |_| false, + get: |_| None, + get_with_styles: |_, _| None, + get_from_styles: |_| None, + materialize: |_, _| {}, + eq: |_, _| true, + } + } +} + +/// A field that has a default value and can be configured via a set rule, but +/// can also present on elements and be present in the constructor. +pub trait SettableField<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: SettableFieldData<Self, I>; +} + +/// Metadata and routines for a [`SettableField`]. +pub struct SettableFieldData<E: SettableField<I>, const I: u8> { + get: fn(&E) -> &Settable<E, I>, + get_mut: fn(&mut E) -> &mut Settable<E, I>, + property: SettablePropertyData<E, I>, +} + +impl<E: SettableField<I>, const I: u8> SettableFieldData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + positional: bool, + get: fn(&E) -> &Settable<E, I>, + get_mut: fn(&mut E) -> &mut Settable<E, I>, + default: fn() -> E::Type, + slot: fn() -> &'static OnceLock<E::Type>, + ) -> Self { + Self { + get, + get_mut, + property: SettablePropertyData::new(name, docs, positional, default, slot), + } + } + + /// Ensures that the property is folded on every access. See the + /// documentation of the [`Fold`] trait for more details. + pub const fn with_fold(mut self) -> Self + where + E::Type: Fold, + { + self.property.fold = Some(E::Type::fold); + self + } + + /// Creates type-erased metadata and routines for a normal settable field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: SettableField<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.property.name, + docs: E::FIELD.property.docs, + positional: E::FIELD.property.positional, + required: false, + variadic: false, + settable: true, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: Some(|| E::default().into_value()), + has: |elem| (E::FIELD.get)(elem).is_set(), + get: |elem| (E::FIELD.get)(elem).as_option().clone().map(|v| v.into_value()), + get_with_styles: |elem, styles| { + Some((E::FIELD.get)(elem).get_cloned(styles).into_value()) + }, + get_from_styles: |styles| { + Some(styles.get_cloned::<E, I>(Field::new()).into_value()) + }, + materialize: |elem, styles| { + if !(E::FIELD.get)(elem).is_set() { + (E::FIELD.get_mut)(elem).set(styles.get_cloned::<E, I>(Field::new())); + } + }, + eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(), + } + } +} + +/// A field that has a default value and can be configured via a set rule, but +/// is never present on elements. +/// +/// This is provided for all `SettableField` impls through a blanket impl. In +/// the case of `#[ghost]` fields, which only live in the style chain and not in +/// elements, it is also implemented manually. +pub trait SettableProperty<const I: u8>: NativeElement { + type Type: Clone; + + const FIELD: SettablePropertyData<Self, I>; + const FOLD: Option<FoldFn<Self::Type>> = Self::FIELD.fold; + + /// Produces an instance of the property's default value. + fn default() -> Self::Type { + // Avoid recreating an expensive instance over and over, but also + // avoid unnecessary lazy initialization for cheap types. + if std::mem::needs_drop::<Self::Type>() { + Self::default_ref().clone() + } else { + (Self::FIELD.default)() + } + } + + /// Produces a static reference to this property's default value. + fn default_ref() -> &'static Self::Type { + (Self::FIELD.slot)().get_or_init(Self::FIELD.default) + } +} + +impl<T, const I: u8> SettableProperty<I> for T +where + T: SettableField<I>, +{ + type Type = <Self as SettableField<I>>::Type; + + const FIELD: SettablePropertyData<Self, I> = + <Self as SettableField<I>>::FIELD.property; +} + +/// Metadata and routines for a [`SettableProperty`]. +pub struct SettablePropertyData<E: SettableProperty<I>, const I: u8> { + name: &'static str, + docs: &'static str, + positional: bool, + default: fn() -> E::Type, + slot: fn() -> &'static OnceLock<E::Type>, + fold: Option<FoldFn<E::Type>>, +} + +impl<E: SettableProperty<I>, const I: u8> SettablePropertyData<E, I> { + /// Creates the data from its parts. This is called in the `#[elem]` macro. + pub const fn new( + name: &'static str, + docs: &'static str, + positional: bool, + default: fn() -> E::Type, + slot: fn() -> &'static OnceLock<E::Type>, + ) -> Self { + Self { name, docs, positional, default, slot, fold: None } + } + + /// Ensures that the property is folded on every access. See the + /// documentation of the [`Fold`] trait for more details. + pub const fn with_fold(self) -> Self + where + E::Type: Fold, + { + Self { fold: Some(E::Type::fold), ..self } + } + + /// Creates type-erased metadata and routines for a `#[ghost]` field. + pub const fn vtable() -> FieldVtable<Packed<E>> + where + E: SettableProperty<I>, + E::Type: Reflect + IntoValue + PartialEq, + { + FieldVtable { + name: E::FIELD.name, + docs: E::FIELD.docs, + positional: E::FIELD.positional, + required: false, + variadic: false, + settable: true, + synthesized: false, + input: || <E::Type as Reflect>::input(), + default: Some(|| E::default().into_value()), + has: |_| false, + get: |_| None, + get_with_styles: |_, styles| { + Some(styles.get_cloned::<E, I>(Field::new()).into_value()) + }, + get_from_styles: |styles| { + Some(styles.get_cloned::<E, I>(Field::new()).into_value()) + }, + materialize: |_, _| {}, + eq: |_, _| true, + } + } +} + +/// A settable property that can be accessed by reference (because it is not +/// folded). +pub trait RefableProperty<const I: u8>: SettableProperty<I> {} + +/// A settable field of an element. +/// +/// The field can be in two states: Unset or present. +/// +/// See [`StyleChain`] for more details about the available accessor methods. +#[derive(Copy, Clone, Hash)] +pub struct Settable<E: NativeElement, const I: u8>(Option<E::Type>) +where + E: SettableProperty<I>; + +impl<E: NativeElement, const I: u8> Settable<E, I> +where + E: SettableProperty<I>, +{ + /// Creates a new unset instance. + pub fn new() -> Self { + Self(None) + } + + /// Sets the instance to a value. + pub fn set(&mut self, value: E::Type) { + self.0 = Some(value); + } + + /// Clears the value from the instance. + pub fn unset(&mut self) { + self.0 = None; + } + + /// Views the type as an [`Option`] which is `Some` if the type is set + /// and `None` if it is unset. + pub fn as_option(&self) -> &Option<E::Type> { + &self.0 + } + + /// Views the type as a mutable [`Option`]. + pub fn as_option_mut(&mut self) -> &mut Option<E::Type> { + &mut self.0 + } + + /// Whether the field is set. + pub fn is_set(&self) -> bool { + self.0.is_some() + } + + /// Retrieves the value given styles. The styles are used if the value is + /// unset. + pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type + where + E::Type: Copy, + { + self.get_cloned(styles) + } + + /// Retrieves and clones the value given styles. The styles are used if the + /// value is unset or if it needs folding. + pub fn get_cloned<'a>(&'a self, styles: StyleChain<'a>) -> E::Type { + if let Some(fold) = E::FOLD { + let mut res = styles.get_cloned::<E, I>(Field::new()); + if let Some(value) = &self.0 { + res = fold(value.clone(), res); + } + res + } else if let Some(value) = &self.0 { + value.clone() + } else { + styles.get_cloned::<E, I>(Field::new()) + } + } + + /// Retrieves a reference to the value given styles. The styles are used if + /// the value is unset. + pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type + where + E: RefableProperty<I>, + { + if let Some(value) = &self.0 { + value + } else { + styles.get_ref::<E, I>(Field::new()) + } + } + + /// Retrieves the value and then immediately [resolves](Resolve) it. + pub fn resolve<'a>(&'a self, styles: StyleChain<'a>) -> <E::Type as Resolve>::Output + where + E::Type: Resolve, + { + self.get_cloned(styles).resolve(styles) + } +} + +impl<E: NativeElement, const I: u8> Debug for Settable<E, I> +where + E: SettableProperty<I>, + E::Type: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<E: NativeElement, const I: u8> Default for Settable<E, I> +where + E: SettableProperty<I>, +{ + fn default() -> Self { + Self(None) + } +} + +impl<E: NativeElement, const I: u8> From<Option<E::Type>> for Settable<E, I> +where + E: SettableProperty<I>, +{ + fn from(value: Option<E::Type>) -> Self { + Self(value) + } +} + +/// An error arising when trying to access a field of content. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum FieldAccessError { + Unknown, + Unset, +} + +impl FieldAccessError { + /// Formats the error message given the content and the field name. + #[cold] + pub fn message(self, content: &Content, field: &str) -> EcoString { + let elem_name = content.elem().name(); + match self { + FieldAccessError::Unknown => { + eco_format!("{elem_name} does not have field {}", field.repr()) + } + FieldAccessError::Unset => { + eco_format!( + "field {} in {elem_name} is not known at this point", + field.repr() + ) + } + } + } + + /// Formats the error message for an `at` calls without a default value. + #[cold] + pub fn message_no_default(self, content: &Content, field: &str) -> EcoString { + let mut msg = self.message(content, field); + msg.push_str(" and no default was specified"); + msg + } +} diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content/mod.rs index 1855bb70..7ba790d8 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content/mod.rs @@ -1,23 +1,33 @@ -use std::any::TypeId; +mod element; +mod field; +mod packed; +mod raw; +mod vtable; + +pub use self::element::*; +pub use self::field::*; +pub use self::packed::Packed; +pub use self::vtable::{ContentVtable, FieldVtable}; +#[doc(inline)] +pub use typst_macros::elem; + use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; use std::iter::{self, Sum}; -use std::marker::PhantomData; -use std::ops::{Add, AddAssign, ControlFlow, Deref, DerefMut}; -use std::sync::Arc; +use std::ops::{Add, AddAssign, ControlFlow}; use comemo::Tracked; use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; + use typst_syntax::Span; -use typst_utils::{fat, singleton, LazyHash, SmallBitSet}; +use typst_utils::singleton; use crate::diag::{SourceResult, StrResult}; use crate::engine::Engine; use crate::foundations::{ - elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label, - NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, - Value, + func, repr, scope, ty, Context, Dict, IntoValue, Label, Property, Recipe, + RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles, Value, }; use crate::introspection::Location; use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides}; @@ -68,43 +78,14 @@ use crate::text::UnderlineElem; /// elements the content is composed of and what fields they have. /// Alternatively, you can inspect the output of the [`repr`] function. #[ty(scope, cast)] -#[derive(Clone, Hash)] -#[allow(clippy::derived_hash_with_manual_eq)] -pub struct Content { - /// The partially element-dependent inner data. - inner: Arc<Inner<dyn Bounds>>, - /// The element's source code location. - span: Span, -} - -/// The inner representation behind the `Arc`. -#[derive(Hash)] -struct Inner<T: ?Sized + 'static> { - /// An optional label attached to the element. - label: Option<Label>, - /// The element's location which identifies it in the layouted output. - location: Option<Location>, - /// Manages the element during realization. - /// - If bit 0 is set, the element is prepared. - /// - If bit n is set, the element is guarded against the n-th show rule - /// recipe from the top of the style chain (counting from 1). - lifecycle: SmallBitSet, - /// The element's raw data. - elem: LazyHash<T>, -} +#[derive(Clone, PartialEq, Hash)] +#[repr(transparent)] +pub struct Content(raw::RawContent); impl Content { /// Creates a new content from an element. pub fn new<T: NativeElement>(elem: T) -> Self { - Self { - inner: Arc::new(Inner { - label: None, - location: None, - lifecycle: SmallBitSet::new(), - elem: elem.into(), - }), - span: Span::detached(), - } + Self(raw::RawContent::new(elem)) } /// Creates a empty sequence content. @@ -114,25 +95,25 @@ impl Content { /// Get the element of this content. pub fn elem(&self) -> Element { - self.inner.elem.dyn_elem() + self.0.elem() } /// Get the span of the content. pub fn span(&self) -> Span { - self.span + self.0.span() } /// Set the span of the content. pub fn spanned(mut self, span: Span) -> Self { - if self.span.is_detached() { - self.span = span; + if self.0.span().is_detached() { + *self.0.span_mut() = span; } self } /// Get the label of the content. pub fn label(&self) -> Option<Label> { - self.inner.label + self.0.meta().label } /// Attach a label to the content. @@ -143,7 +124,7 @@ impl Content { /// Set the label of the content. pub fn set_label(&mut self, label: Label) { - self.make_mut().label = Some(label); + self.0.meta_mut().label = Some(label); } /// Assigns a location to the content. @@ -159,28 +140,28 @@ impl Content { /// Set the location of the content. pub fn set_location(&mut self, location: Location) { - self.make_mut().location = Some(location); + self.0.meta_mut().location = Some(location); } /// Check whether a show rule recipe is disabled. pub fn is_guarded(&self, index: RecipeIndex) -> bool { - self.inner.lifecycle.contains(index.0) + self.0.meta().lifecycle.contains(index.0) } /// Disable a show rule recipe. pub fn guarded(mut self, index: RecipeIndex) -> Self { - self.make_mut().lifecycle.insert(index.0); + self.0.meta_mut().lifecycle.insert(index.0); self } /// Whether this content has already been prepared. pub fn is_prepared(&self) -> bool { - self.inner.lifecycle.contains(0) + self.0.meta().lifecycle.contains(0) } /// Mark this content as prepared. pub fn mark_prepared(&mut self) { - self.make_mut().lifecycle.insert(0); + self.0.meta_mut().lifecycle.insert(0); } /// Get a field by ID. @@ -198,9 +179,14 @@ impl Content { return Ok(label.into_value()); } } - match styles { - Some(styles) => self.inner.elem.field_with_styles(id, styles), - None => self.inner.elem.field(id), + + match self.0.handle().field(id) { + Some(handle) => match styles { + Some(styles) => handle.get_with_styles(styles), + None => handle.get(), + } + .ok_or(FieldAccessError::Unset), + None => Err(FieldAccessError::Unknown), } } @@ -215,8 +201,11 @@ impl Content { .map(|label| label.into_value()) .ok_or(FieldAccessError::Unknown); } - let id = self.elem().field_id(name).ok_or(FieldAccessError::Unknown)?; - self.get(id, None) + + match self.elem().field_id(name).and_then(|id| self.0.handle().field(id)) { + Some(handle) => handle.get().ok_or(FieldAccessError::Unset), + None => Err(FieldAccessError::Unknown), + } } /// Get a field by ID, returning a missing field error if it does not exist. @@ -240,7 +229,9 @@ impl Content { /// Resolve all fields with the styles and save them in-place. pub fn materialize(&mut self, styles: StyleChain) { - self.make_mut().elem.materialize(styles); + for id in 0..self.elem().vtable().fields.len() as u8 { + self.0.handle_mut().field(id).unwrap().materialize(styles); + } } /// Create a new sequence element from multiples elements. @@ -257,7 +248,7 @@ impl Content { /// Whether the contained element is of type `T`. pub fn is<T: NativeElement>(&self) -> bool { - self.inner.elem.dyn_type_id() == TypeId::of::<T>() + self.0.is::<T>() } /// Downcasts the element to a packed value. @@ -280,16 +271,6 @@ impl Content { self.into_packed::<T>().map(Packed::unpack) } - /// Makes sure the content is not shared and returns a mutable reference to - /// the inner data. - fn make_mut(&mut self) -> &mut Inner<dyn Bounds> { - let arc = &mut self.inner; - if Arc::strong_count(arc) > 1 || Arc::weak_count(arc) > 0 { - *self = arc.elem.dyn_clone(arc, self.span); - } - Arc::get_mut(&mut self.inner).unwrap() - } - /// Whether the contained element has the given capability. pub fn can<C>(&self) -> bool where @@ -304,13 +285,7 @@ impl Content { where C: ?Sized + 'static, { - // Safety: The vtable comes from the `Capable` implementation which - // guarantees to return a matching vtable for `Packed<T>` and `C`. - // Since any `Packed<T>` is a repr(transparent) `Content`, we can also - // use a `*const Content` pointer. - let vtable = self.elem().vtable()(TypeId::of::<C>())?; - let data = self as *const Content as *const (); - Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) }) + self.0.with::<C>() } /// Cast to a mutable trait object if the contained element has the given @@ -319,18 +294,7 @@ impl Content { where C: ?Sized + 'static, { - // Safety: The vtable comes from the `Capable` implementation which - // guarantees to return a matching vtable for `Packed<T>` and `C`. - // Since any `Packed<T>` is a repr(transparent) `Content`, we can also - // use a `*const Content` pointer. - // - // The resulting trait object contains an `&mut Packed<T>`. We do _not_ - // need to ensure that we hold the only reference to the `Arc` here - // because `Packed<T>`'s DerefMut impl will take care of that if - // mutable access is required. - let vtable = self.elem().vtable()(TypeId::of::<C>())?; - let data = self as *mut Content as *mut (); - Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) }) + self.0.with_mut::<C>() } /// Whether the content is an empty sequence. @@ -372,6 +336,15 @@ impl Content { Self::sequence(std::iter::repeat_with(|| self.clone()).take(count)) } + /// Sets a style property on the content. + pub fn set<E, const I: u8>(self, field: Field<E, I>, value: E::Type) -> Self + where + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, + { + self.styled(Property::new(field, value)) + } + /// Style this content with a style entry. pub fn styled(mut self, style: impl Into<Style>) -> Self { if let Some(style_elem) = self.to_packed_mut::<StyledElem>() { @@ -476,7 +449,7 @@ impl Content { // Call f on the element itself before recursively iterating its fields. f(self.clone())?; - for (_, value) in self.inner.elem.fields() { + for (_, value) in self.fields() { walk_value(value, f)?; } ControlFlow::Continue(()) @@ -504,12 +477,12 @@ impl Content { /// Link the content somewhere. pub fn linked(self, dest: Destination) -> Self { - self.styled(LinkElem::set_current(Some(dest))) + self.set(LinkElem::current, Some(dest)) } /// Set alignments for this content. pub fn aligned(self, align: Alignment) -> Self { - self.styled(AlignElem::set_alignment(align)) + self.set(AlignElem::alignment, align) } /// Pad this content at the sides. @@ -562,7 +535,10 @@ impl Content { return false; }; - self.inner.elem.has(id) + match self.0.handle().field(id) { + Some(field) => field.has(), + None => false, + } } /// Access the specified field on the content. Returns the default value if @@ -592,7 +568,12 @@ impl Content { /// ``` #[func] pub fn fields(&self) -> Dict { - let mut dict = self.inner.elem.fields(); + let mut dict = Dict::new(); + for field in self.0.handle().fields() { + if let Some(value) = field.get() { + dict.insert(field.name.into(), value); + } + } if let Some(label) = self.label() { dict.insert("label".into(), label.into_value()); } @@ -605,7 +586,7 @@ impl Content { /// used with [counters]($counter), [state] and [queries]($query). #[func] pub fn location(&self) -> Option<Location> { - self.inner.location + self.0.meta().location } } @@ -617,7 +598,7 @@ impl Default for Content { impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.inner.elem.fmt(f) + self.0.fmt(f) } } @@ -627,16 +608,22 @@ impl<T: NativeElement> From<T> for Content { } } -impl PartialEq for Content { - fn eq(&self, other: &Self) -> bool { - // Additional short circuit for different elements. - self.elem() == other.elem() && self.inner.elem.dyn_eq(other) - } -} - impl Repr for Content { fn repr(&self) -> EcoString { - self.inner.elem.repr() + self.0.handle().repr().unwrap_or_else(|| { + let fields = self + .0 + .handle() + .fields() + .filter_map(|field| field.get().map(|v| (field.name, v.repr()))) + .map(|(name, value)| eco_format!("{name}: {value}")) + .collect::<Vec<_>>(); + eco_format!( + "{}{}", + self.elem().name(), + repr::pretty_array_like(&fields, false), + ) + }) } } @@ -717,190 +704,8 @@ impl Serialize for Content { } } -/// The trait that combines all the other traits into a trait object. -trait Bounds: Debug + Repr + Fields + Send + Sync + 'static { - fn dyn_type_id(&self) -> TypeId; - fn dyn_elem(&self) -> Element; - fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content; - fn dyn_hash(&self, hasher: &mut dyn Hasher); - fn dyn_eq(&self, other: &Content) -> bool; -} - -impl<T: NativeElement> Bounds for T { - fn dyn_type_id(&self) -> TypeId { - TypeId::of::<Self>() - } - - fn dyn_elem(&self) -> Element { - Self::elem() - } - - fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content { - Content { - inner: Arc::new(Inner { - label: inner.label, - location: inner.location, - lifecycle: inner.lifecycle.clone(), - elem: LazyHash::reuse(self.clone(), &inner.elem), - }), - span, - } - } - - fn dyn_hash(&self, mut state: &mut dyn Hasher) { - TypeId::of::<Self>().hash(&mut state); - self.hash(&mut state); - } - - fn dyn_eq(&self, other: &Content) -> bool { - let Some(other) = other.to_packed::<Self>() else { - return false; - }; - *self == **other - } -} - -impl Hash for dyn Bounds { - fn hash<H: Hasher>(&self, state: &mut H) { - self.dyn_hash(state); - } -} - -/// A packed element of a static type. -#[derive(Clone, PartialEq, Hash)] -#[repr(transparent)] -pub struct Packed<T: NativeElement>( - /// Invariant: Must be of type `T`. - Content, - PhantomData<T>, -); - -impl<T: NativeElement> Packed<T> { - /// Pack element while retaining its static type. - pub fn new(element: T) -> Self { - // Safety: The element is known to be of type `T`. - Packed(element.pack(), PhantomData) - } - - /// Try to cast type-erased content into a statically known packed element. - pub fn from_ref(content: &Content) -> Option<&Self> { - if content.is::<T>() { - // Safety: - // - We have checked the type. - // - Packed<T> is repr(transparent). - return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) }); - } - None - } - - /// Try to cast type-erased content into a statically known packed element. - pub fn from_mut(content: &mut Content) -> Option<&mut Self> { - if content.is::<T>() { - // Safety: - // - We have checked the type. - // - Packed<T> is repr(transparent). - return Some(unsafe { - std::mem::transmute::<&mut Content, &mut Packed<T>>(content) - }); - } - None - } - - /// Try to cast type-erased content into a statically known packed element. - pub fn from_owned(content: Content) -> Result<Self, Content> { - if content.is::<T>() { - // Safety: - // - We have checked the type. - // - Packed<T> is repr(transparent). - return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) }); - } - Err(content) - } - - /// Pack back into content. - pub fn pack(self) -> Content { - self.0 - } - - /// Extract the raw underlying element. - pub fn unpack(self) -> T { - // This function doesn't yet need owned self, but might in the future. - (*self).clone() - } - - /// The element's span. - pub fn span(&self) -> Span { - self.0.span() - } - - /// Set the span of the element. - pub fn spanned(self, span: Span) -> Self { - Self(self.0.spanned(span), PhantomData) - } - - /// Accesses the label of the element. - pub fn label(&self) -> Option<Label> { - self.0.label() - } - - /// Accesses the location of the element. - pub fn location(&self) -> Option<Location> { - self.0.location() - } - - /// Sets the location of the element. - pub fn set_location(&mut self, location: Location) { - self.0.set_location(location); - } -} - -impl<T: NativeElement> AsRef<T> for Packed<T> { - fn as_ref(&self) -> &T { - self - } -} - -impl<T: NativeElement> AsMut<T> for Packed<T> { - fn as_mut(&mut self) -> &mut T { - self - } -} - -impl<T: NativeElement> Deref for Packed<T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - // Safety: - // - Packed<T> guarantees that the content trait object wraps - // an element of type `T`. - // - This downcast works the same way as dyn Any's does. We can't reuse - // that one because we don't want to pay the cost for every deref. - let elem = &*self.0.inner.elem; - unsafe { &*(elem as *const dyn Bounds as *const T) } - } -} - -impl<T: NativeElement> DerefMut for Packed<T> { - fn deref_mut(&mut self) -> &mut Self::Target { - // Safety: - // - Packed<T> guarantees that the content trait object wraps - // an element of type `T`. - // - We have guaranteed unique access thanks to `make_mut`. - // - This downcast works the same way as dyn Any's does. We can't reuse - // that one because we don't want to pay the cost for every deref. - let elem = &mut *self.0.make_mut().elem; - unsafe { &mut *(elem as *mut dyn Bounds as *mut T) } - } -} - -impl<T: NativeElement + Debug> Debug for Packed<T> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - /// A sequence of content. -#[elem(Debug, Repr, PartialEq)] +#[elem(Debug, Repr)] pub struct SequenceElem { /// The elements. #[required] @@ -922,19 +727,13 @@ impl Default for SequenceElem { } } -impl PartialEq for SequenceElem { - fn eq(&self, other: &Self) -> bool { - self.children.iter().eq(other.children.iter()) - } -} - impl Repr for SequenceElem { fn repr(&self) -> EcoString { if self.children.is_empty() { "[]".into() } else { let elements = crate::foundations::repr::pretty_array_like( - &self.children.iter().map(|c| c.inner.elem.repr()).collect::<Vec<_>>(), + &self.children.iter().map(|c| c.repr()).collect::<Vec<_>>(), false, ); eco_format!("sequence{}", elements) @@ -974,49 +773,8 @@ impl Repr for StyledElem { } } -/// Tries to extract the plain-text representation of the element. -pub trait PlainText { - /// Write this element's plain text into the given buffer. - fn plain_text(&self, text: &mut EcoString); -} - -/// An error arising when trying to access a field of content. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum FieldAccessError { - Unknown, - Unset, - Internal, -} - -impl FieldAccessError { - /// Formats the error message given the content and the field name. - #[cold] - pub fn message(self, content: &Content, field: &str) -> EcoString { - let elem_name = content.elem().name(); - match self { - FieldAccessError::Unknown => { - eco_format!("{elem_name} does not have field {}", field.repr()) - } - FieldAccessError::Unset => { - eco_format!( - "field {} in {elem_name} is not known at this point", - field.repr() - ) - } - FieldAccessError::Internal => { - eco_format!( - "internal error when accessing field {} in {elem_name} – this is a bug", - field.repr() - ) - } - } - } - - /// Formats the error message for an `at` calls without a default value. - #[cold] - pub fn message_no_default(self, content: &Content, field: &str) -> EcoString { - let mut msg = self.message(content, field); - msg.push_str(" and no default was specified"); - msg +impl<T: NativeElement> IntoValue for T { + fn into_value(self) -> Value { + Value::Content(self.pack()) } } diff --git a/crates/typst-library/src/foundations/content/packed.rs b/crates/typst-library/src/foundations/content/packed.rs new file mode 100644 index 00000000..71bb66a9 --- /dev/null +++ b/crates/typst-library/src/foundations/content/packed.rs @@ -0,0 +1,147 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; + +use typst_syntax::Span; + +use crate::foundations::{Content, Label, NativeElement}; +use crate::introspection::Location; + +/// A packed element of a static type. +#[derive(Clone)] +#[repr(transparent)] +pub struct Packed<T: NativeElement>( + /// Invariant: Must be of type `T`. + Content, + PhantomData<T>, +); + +impl<T: NativeElement> Packed<T> { + /// Pack element while retaining its static type. + pub fn new(element: T) -> Self { + // Safety: The element is known to be of type `T`. + Packed(element.pack(), PhantomData) + } + + /// Try to cast type-erased content into a statically known packed element. + pub fn from_ref(content: &Content) -> Option<&Self> { + if content.is::<T>() { + // Safety: + // - We have checked the type. + // - Packed<T> is repr(transparent). + return Some(unsafe { std::mem::transmute::<&Content, &Packed<T>>(content) }); + } + None + } + + /// Try to cast type-erased content into a statically known packed element. + pub fn from_mut(content: &mut Content) -> Option<&mut Self> { + if content.is::<T>() { + // Safety: + // - We have checked the type. + // - Packed<T> is repr(transparent). + return Some(unsafe { + std::mem::transmute::<&mut Content, &mut Packed<T>>(content) + }); + } + None + } + + /// Try to cast type-erased content into a statically known packed element. + pub fn from_owned(content: Content) -> Result<Self, Content> { + if content.is::<T>() { + // Safety: + // - We have checked the type. + // - Packed<T> is repr(transparent). + return Ok(unsafe { std::mem::transmute::<Content, Packed<T>>(content) }); + } + Err(content) + } + + /// Pack back into content. + pub fn pack(self) -> Content { + self.0 + } + + /// Extract the raw underlying element. + pub fn unpack(self) -> T { + // This function doesn't yet need owned self, but might in the future. + (*self).clone() + } + + /// The element's span. + pub fn span(&self) -> Span { + self.0.span() + } + + /// Set the span of the element. + pub fn spanned(self, span: Span) -> Self { + Self(self.0.spanned(span), PhantomData) + } + + /// Accesses the label of the element. + pub fn label(&self) -> Option<Label> { + self.0.label() + } + + /// Accesses the location of the element. + pub fn location(&self) -> Option<Location> { + self.0.location() + } + + /// Sets the location of the element. + pub fn set_location(&mut self, location: Location) { + self.0.set_location(location); + } + + pub fn as_content(&self) -> &Content { + &self.0 + } +} + +impl<T: NativeElement> AsRef<T> for Packed<T> { + fn as_ref(&self) -> &T { + self + } +} + +impl<T: NativeElement> AsMut<T> for Packed<T> { + fn as_mut(&mut self) -> &mut T { + self + } +} + +impl<T: NativeElement> Deref for Packed<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // Safety: Packed<T> guarantees that the content is of element type `T`. + unsafe { (self.0).0.data::<T>() } + } +} + +impl<T: NativeElement> DerefMut for Packed<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: Packed<T> guarantees that the content is of element type `T`. + unsafe { (self.0).0.data_mut::<T>() } + } +} + +impl<T: NativeElement + Debug> Debug for Packed<T> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<T: NativeElement> PartialEq for Packed<T> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl<T: NativeElement> Hash for Packed<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.0.hash(state); + } +} diff --git a/crates/typst-library/src/foundations/content/raw.rs b/crates/typst-library/src/foundations/content/raw.rs new file mode 100644 index 00000000..f5dfffd7 --- /dev/null +++ b/crates/typst-library/src/foundations/content/raw.rs @@ -0,0 +1,426 @@ +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::ptr::NonNull; +use std::sync::atomic::{self, AtomicUsize, Ordering}; + +use typst_syntax::Span; +use typst_utils::{fat, HashLock, SmallBitSet}; + +use super::vtable; +use crate::foundations::{Element, Label, NativeElement, Packed}; +use crate::introspection::Location; + +/// The raw, low-level implementation of content. +/// +/// The `ptr` + `elem` fields implement a fat pointer setup similar to an +/// `Arc<Inner<dyn Trait>>`, but in a manual way, allowing us to have a custom +/// [vtable]. +pub struct RawContent { + /// A type-erased pointer to an allocation containing two things: + /// - A header that is the same for all elements + /// - Element-specific `data` that holds the specific element + /// + /// This pointer is valid for both a `Header` and an `Inner<E>` where + /// `E::ELEM == self.elem` and can be freely cast between both. This is + /// possible because + /// - `Inner<E>` is `repr(C)` + /// - The first field of `Inner<E>` is `Header` + /// - ISO/IEC 9899:TC2 C standard § 6.7.2.1 - 13 states that a pointer to a + /// structure "points to its initial member" with no padding at the start + ptr: NonNull<Header>, + /// Describes which kind of element this content holds. This is used for + /// + /// - Direct comparisons, e.g. `is::<HeadingElem>()` + /// - Behavior: An `Element` is just a pointer to a `ContentVtable` + /// containing not just data, but also function pointers for various + /// element-specific operations that can be performed + /// + /// It is absolutely crucial that `elem == <E as NativeElement>::ELEM` for + /// `Inner<E>` pointed to by `ptr`. Otherwise, things will go very wrong + /// since we'd be using the wrong vtable. + elem: Element, + /// The content's span. + span: Span, +} + +/// The allocated part of an element's representation. +/// +/// This is `repr(C)` to ensure that a pointer to the whole structure may be +/// cast to a pointer to its first field. +#[repr(C)] +struct Inner<E> { + /// It is crucial that this is the first field because we cast between + /// pointers to `Inner<E>` and pointers to `Header`. See the documentation + /// of `RawContent::ptr` for more details. + header: Header, + /// The element struct. E.g. `E = HeadingElem`. + data: E, +} + +/// The header that is shared by all elements. +struct Header { + /// The element's reference count. This works just like for `Arc`. + /// Unfortunately, we have to reimplement reference counting because we + /// have a custom fat pointer and `Arc` wouldn't know how to drop its + /// contents. Something with `ManuallyDrop<Arc<_>>` might also work, but at + /// that point we're not gaining much and with the way it's implemented now + /// we can also skip the unnecessary weak reference count. + refs: AtomicUsize, + /// Metadata for the element. + meta: Meta, + /// A cell for memoizing the hash of just the `data` part of the content. + hash: HashLock, +} + +/// Metadata that elements can hold. +#[derive(Clone, Hash)] +pub(super) struct Meta { + /// An optional label attached to the element. + pub label: Option<Label>, + /// The element's location which identifies it in the laid-out output. + pub location: Option<Location>, + /// Manages the element during realization. + /// - If bit 0 is set, the element is prepared. + /// - If bit n is set, the element is guarded against the n-th show rule + /// recipe from the top of the style chain (counting from 1). + pub lifecycle: SmallBitSet, +} + +impl RawContent { + /// Creates raw content wrapping an element, with all metadata set to + /// default (including a detached span). + pub(super) fn new<E: NativeElement>(data: E) -> Self { + Self::create( + data, + Meta { + label: None, + location: None, + lifecycle: SmallBitSet::new(), + }, + HashLock::new(), + Span::detached(), + ) + } + + /// Creates and allocates raw content. + fn create<E: NativeElement>(data: E, meta: Meta, hash: HashLock, span: Span) -> Self { + let raw = Box::into_raw(Box::<Inner<E>>::new(Inner { + header: Header { refs: AtomicUsize::new(1), meta, hash }, + data, + })); + + // Safety: `Box` always holds a non-null pointer. See also + // `Box::into_non_null` (which is unstable). + let non_null = unsafe { NonNull::new_unchecked(raw) }; + + // Safety: See `RawContent::ptr`. + let ptr = non_null.cast::<Header>(); + + Self { ptr, elem: E::ELEM, span } + } + + /// Destroys raw content and deallocates. + /// + /// # Safety + /// - The reference count must be zero. + /// - The raw content must be be of type `E`. + pub(super) unsafe fn drop_impl<E: NativeElement>(&mut self) { + debug_assert_eq!(self.header().refs.load(Ordering::Relaxed), 0); + + // Safety: + // - The caller guarantees that the content is of type `E`. + // - Thus, `ptr` must have been created from `Box<Inner<E>>` (see + // `RawContent::ptr`). + // - And to clean it up, we can just reproduce our box. + unsafe { + let ptr = self.ptr.cast::<Inner<E>>(); + drop(Box::<Inner<E>>::from_raw(ptr.as_ptr())); + } + } + + /// Clones a packed element into new raw content. + pub(super) fn clone_impl<E: NativeElement>(elem: &Packed<E>) -> Self { + let raw = &elem.as_content().0; + let header = raw.header(); + RawContent::create( + elem.as_ref().clone(), + header.meta.clone(), + header.hash.clone(), + raw.span, + ) + } + + /// Accesses the header part of the raw content. + fn header(&self) -> &Header { + // Safety: `self.ptr` is a valid pointer to a header structure. + unsafe { self.ptr.as_ref() } + } + + /// Mutably accesses the header part of the raw content. + fn header_mut(&mut self) -> &mut Header { + self.make_unique(); + + // Safety: + // - `self.ptr` is a valid pointer to a header structure. + // - We have unique access to the backing allocation (just ensured). + unsafe { self.ptr.as_mut() } + } + + /// Retrieves the contained element **without checking that the content is + /// of the correct type.** + /// + /// # Safety + /// This must be preceded by a check to [`is`]. The safe API for this is + /// [`Content::to_packed`] and the [`Packed`] struct. + pub(super) unsafe fn data<E: NativeElement>(&self) -> &E { + debug_assert!(self.is::<E>()); + + // Safety: + // - The caller guarantees that the content is of type `E`. + // - `self.ptr` is a valid pointer to an `Inner<E>` (see + // `RawContent::ptr`). + unsafe { &self.ptr.cast::<Inner<E>>().as_ref().data } + } + + /// Retrieves the contained element mutably **without checking that the + /// content is of the correct type.** + /// + /// Ensures that the element's allocation is unique. + /// + /// # Safety + /// This must be preceded by a check to [`is`]. The safe API for this is + /// [`Content::to_packed_mut`] and the [`Packed`] struct. + pub(super) unsafe fn data_mut<E: NativeElement>(&mut self) -> &mut E { + debug_assert!(self.is::<E>()); + + // Ensure that the memoized hash is reset because we may mutate the + // element. + self.header_mut().hash.reset(); + + // Safety: + // - The caller guarantees that the content is of type `E`. + // - `self.ptr` is a valid pointer to an `Inner<E>` (see + // `RawContent::ptr`). + // - We have unique access to the backing allocation (due to header_mut). + unsafe { &mut self.ptr.cast::<Inner<E>>().as_mut().data } + } + + /// Ensures that we have unique access to the backing allocation by cloning + /// if the reference count exceeds 1. This is used before performing + /// mutable operations, implementing a clone-on-write scheme. + fn make_unique(&mut self) { + if self.header().refs.load(Ordering::Relaxed) > 1 { + *self = self.handle().clone(); + } + } + + /// Retrieves the element this content is for. + pub(super) fn elem(&self) -> Element { + self.elem + } + + /// Whether this content holds an element of type `E`. + pub(super) fn is<E: NativeElement>(&self) -> bool { + self.elem == E::ELEM + } + + /// Retrieves the content's span. + pub(super) fn span(&self) -> Span { + self.span + } + + /// Retrieves the content's span mutably. + pub(super) fn span_mut(&mut self) -> &mut Span { + &mut self.span + } + + /// Retrieves the content's metadata. + pub(super) fn meta(&self) -> &Meta { + &self.header().meta + } + + /// Retrieves the content's metadata mutably. + pub(super) fn meta_mut(&mut self) -> &mut Meta { + &mut self.header_mut().meta + } + + /// Casts into a trait object for a given trait if the packed element + /// implements said trait. + pub(super) fn with<C>(&self) -> Option<&C> + where + C: ?Sized + 'static, + { + // Safety: The vtable comes from the `Capable` implementation which + // guarantees to return a matching vtable for `Packed<T>` and `C`. Since + // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`, + // we can also use a `*const RawContent` pointer. + let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?; + let data = self as *const Self as *const (); + Some(unsafe { &*fat::from_raw_parts(data, vtable.as_ptr()) }) + } + + /// Casts into a mutable trait object for a given trait if the packed + /// element implements said trait. + pub(super) fn with_mut<C>(&mut self) -> Option<&mut C> + where + C: ?Sized + 'static, + { + // Safety: The vtable comes from the `Capable` implementation which + // guarantees to return a matching vtable for `Packed<T>` and `C`. Since + // any `Packed<T>` is repr(transparent) with `Content` and `RawContent`, + // we can also use a `*const Content` pointer. + // + // The resulting trait object contains an `&mut Packed<T>`. We do _not_ + // need to ensure that we hold the only reference to the `Arc` here + // because `Packed<T>`'s DerefMut impl will take care of that if mutable + // access is required. + let vtable = (self.elem.vtable().capability)(TypeId::of::<C>())?; + let data = self as *mut Self as *mut (); + Some(unsafe { &mut *fat::from_raw_parts_mut(data, vtable.as_ptr()) }) + } +} + +impl RawContent { + /// Retrieves the element's vtable. + pub(super) fn handle(&self) -> vtable::ContentHandle<&RawContent> { + // Safety `self.elem.vtable()` is a matching vtable for `self`. + unsafe { vtable::Handle::new(self, self.elem.vtable()) } + } + + /// Retrieves the element's vtable. + pub(super) fn handle_mut(&mut self) -> vtable::ContentHandle<&mut RawContent> { + // Safety `self.elem.vtable()` is a matching vtable for `self`. + unsafe { vtable::Handle::new(self, self.elem.vtable()) } + } + + /// Retrieves the element's vtable. + pub(super) fn handle_pair<'a, 'b>( + &'a self, + other: &'b RawContent, + ) -> Option<vtable::ContentHandle<(&'a RawContent, &'b RawContent)>> { + (self.elem == other.elem).then(|| { + // Safety: + // - `self.elem.vtable()` is a matching vtable for `self`. + // - It's also matching for `other` because `self.elem == other.elem`. + unsafe { vtable::Handle::new((self, other), self.elem.vtable()) } + }) + } +} + +impl Debug for RawContent { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.handle().debug(f) + } +} + +impl Clone for RawContent { + fn clone(&self) -> Self { + // See Arc's clone impl for details about memory ordering. + let prev = self.header().refs.fetch_add(1, Ordering::Relaxed); + + // See Arc's clone impl details about guarding against incredibly + // degenerate programs. + if prev > isize::MAX as usize { + ref_count_overflow(self.ptr, self.elem, self.span); + } + + Self { ptr: self.ptr, elem: self.elem, span: self.span } + } +} + +impl Drop for RawContent { + fn drop(&mut self) { + // Drop our ref-count. If there was more than one content before + // (including this one), we shouldn't deallocate. See Arc's drop impl + // for details about memory ordering. + if self.header().refs.fetch_sub(1, Ordering::Release) != 1 { + return; + } + + // See Arc's drop impl for details. + atomic::fence(Ordering::Acquire); + + // Safety: + // No other content references the backing allocation (just checked) + unsafe { + self.handle_mut().drop(); + } + } +} + +impl PartialEq for RawContent { + fn eq(&self, other: &Self) -> bool { + let Some(handle) = self.handle_pair(other) else { return false }; + handle + .eq() + .unwrap_or_else(|| handle.fields().all(|handle| handle.eq())) + } +} + +impl Hash for RawContent { + fn hash<H: Hasher>(&self, state: &mut H) { + self.elem.hash(state); + let header = self.header(); + header.meta.hash(state); + header.hash.get_or_insert_with(|| self.handle().hash()).hash(state); + self.span.hash(state); + } +} + +// Safety: +// - Works like `Arc`. +// - `NativeElement` implies `Send` and `Sync`, see below. +unsafe impl Sync for RawContent {} +unsafe impl Send for RawContent {} + +fn _ensure_send_sync<T: NativeElement>() { + fn needs_send_sync<T: Send + Sync>() {} + needs_send_sync::<T>(); +} + +#[cold] +fn ref_count_overflow(ptr: NonNull<Header>, elem: Element, span: Span) -> ! { + // Drop to decrement the ref count to counter the increment in `clone()` + drop(RawContent { ptr, elem, span }); + panic!("reference count overflow"); +} + +#[cfg(test)] +mod tests { + use crate::foundations::{NativeElement, Repr, StyleChain, Value}; + use crate::introspection::Location; + use crate::model::HeadingElem; + use crate::text::TextElem; + + #[test] + fn test_miri() { + let styles = StyleChain::default(); + + let mut first = HeadingElem::new(TextElem::packed("Hi!")).with_offset(2).pack(); + let hash1 = typst_utils::hash128(&first); + first.set_location(Location::new(10)); + let _ = format!("{first:?}"); + let _ = first.repr(); + + assert!(first.is::<HeadingElem>()); + assert!(!first.is::<TextElem>()); + assert_eq!(first.to_packed::<TextElem>(), None); + assert_eq!(first.location(), Some(Location::new(10))); + assert_eq!(first.field_by_name("offset"), Ok(Value::Int(2))); + assert!(!first.has("depth".into())); + + let second = first.clone(); + first.materialize(styles); + + let first_packed = first.to_packed::<HeadingElem>().unwrap(); + let second_packed = second.to_packed::<HeadingElem>().unwrap(); + + assert!(first.has("depth".into())); + assert!(!second.has("depth".into())); + assert!(first_packed.depth.is_set()); + assert!(!second_packed.depth.is_set()); + assert_ne!(first, second); + assert_ne!(hash1, typst_utils::hash128(&first)); + } +} diff --git a/crates/typst-library/src/foundations/content/vtable.rs b/crates/typst-library/src/foundations/content/vtable.rs new file mode 100644 index 00000000..fdc9d423 --- /dev/null +++ b/crates/typst-library/src/foundations/content/vtable.rs @@ -0,0 +1,383 @@ +//! A custom [vtable] implementation for content. +//! +//! This is similar to what is generated by the Rust compiler under the hood +//! when using trait objects. However, ours has two key advantages: +//! +//! - It can store a _slice_ of sub-vtables for field-specific operations. +//! - It can store not only methods, but also plain data, allowing us to access +//! that data without going through dynamic dispatch. +//! +//! Because our vtable pointers are backed by `static` variables, we can also +//! perform checks for element types by comparing raw vtable pointers giving us +//! `RawContent::is` without dynamic dispatch. +//! +//! Overall, the custom vtable gives us just a little more flexibility and +//! optimizability than using built-in trait objects. +//! +//! Note that all vtable methods receive elements of type `Packed<E>`, but some +//! only perform actions on the `E` itself, with the shared part kept outside of +//! the vtable (e.g. `hash`), while some perform the full action (e.g. `clone` +//! as it needs to return new, fully populated raw content). Which one it is, is +//! documented for each. +//! +//! # Safety +//! This module contains a lot of `unsafe` keywords, but almost all of it is the +//! same and quite straightfoward. All function pointers that operate on a +//! specific element type are marked as unsafe. In combination with `repr(C)`, +//! this grants us the ability to safely transmute a `ContentVtable<Packed<E>>` +//! into a `ContentVtable<RawContent>` (or just short `ContentVtable`). Callers +//! of functions marked as unsafe have to guarantee that the `ContentVtable` was +//! transmuted from the same `E` as the RawContent was constructed from. The +//! `Handle` struct provides a safe access layer, moving the guarantee that the +//! vtable is matching into a single spot. +//! +//! [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table + +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; +use std::ptr::NonNull; + +use ecow::EcoString; + +use super::raw::RawContent; +use crate::diag::SourceResult; +use crate::engine::Engine; +use crate::foundations::{ + Args, CastInfo, Construct, Content, LazyElementStore, NativeElement, NativeScope, + Packed, Repr, Scope, Set, StyleChain, Styles, Value, +}; +use crate::text::{Lang, LocalName, Region}; + +/// Encapsulates content and a vtable, granting safe access to vtable operations. +pub(super) struct Handle<T, V: 'static>(T, &'static V); + +impl<T, V> Handle<T, V> { + /// Produces a new handle from content and a vtable. + /// + /// # Safety + /// The content and vtable must be matching, i.e. `vtable` must be derived + /// from the content's vtable. + pub(super) unsafe fn new(content: T, vtable: &'static V) -> Self { + Self(content, vtable) + } +} + +impl<T, V> Deref for Handle<T, V> { + type Target = V; + + fn deref(&self) -> &Self::Target { + self.1 + } +} + +pub(super) type ContentHandle<T> = Handle<T, ContentVtable>; +pub(super) type FieldHandle<T> = Handle<T, FieldVtable>; + +/// A vtable for performing element-specific actions on type-erased content. +/// Also contains general metadata for the specific element. +#[repr(C)] +pub struct ContentVtable<T: 'static = RawContent> { + /// The element's normal name, as in code. + pub(super) name: &'static str, + /// The element's title-cased name. + pub(super) title: &'static str, + /// The element's documentation (as Markdown). + pub(super) docs: &'static str, + /// Search keywords for the documentation. + pub(super) keywords: &'static [&'static str], + + /// Subvtables for all fields of the element. + pub(super) fields: &'static [FieldVtable<T>], + /// Determines the ID for a field name. This is a separate function instead + /// of searching through `fields` so that Rust can generate optimized code + /// for the string matching. + pub(super) field_id: fn(name: &str) -> Option<u8>, + + /// The constructor of the element. + pub(super) construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>, + /// The set rule of the element. + pub(super) set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>, + /// The element's local name in a specific lang-region pairing. + pub(super) local_name: Option<fn(Lang, Option<Region>) -> &'static str>, + /// Produces the associated [`Scope`] of the element. + pub(super) scope: fn() -> Scope, + /// If the `capability` function returns `Some(p)`, then `p` must be a valid + /// pointer to a native Rust vtable of `Packed<Self>` w.r.t to the trait `C` + /// where `capability` is `TypeId::of::<dyn C>()`. + pub(super) capability: fn(capability: TypeId) -> Option<NonNull<()>>, + + /// The `Drop` impl (for the whole raw content). The content must have a + /// reference count of zero and may not be used anymore after `drop` was + /// called. + pub(super) drop: unsafe fn(&mut RawContent), + /// The `Clone` impl (for the whole raw content). + pub(super) clone: unsafe fn(&T) -> RawContent, + /// The `Hash` impl (for just the element). + pub(super) hash: unsafe fn(&T) -> u128, + /// The `Debug` impl (for just the element). + pub(super) debug: unsafe fn(&T, &mut Formatter) -> fmt::Result, + /// The `PartialEq` impl (for just the element). If this is `None`, + /// field-wise equality checks (via `FieldVtable`) should be performed. + pub(super) eq: Option<unsafe fn(&T, &T) -> bool>, + /// The `Repr` impl (for just the element). If this is `None`, a generic + /// name + fields representation should be produced. + pub(super) repr: Option<unsafe fn(&T) -> EcoString>, + + /// Produces a reference to a `static` variable holding a `LazyElementStore` + /// that is unique for this element and can be populated with data that is + /// somewhat costly to initialize at runtime and shouldn't be initialized + /// over and over again. Must be a function rather than a direct reference + /// so that we can store the vtable in a `const` without Rust complaining + /// about the presence of interior mutability. + pub(super) store: fn() -> &'static LazyElementStore, +} + +impl ContentVtable { + /// Creates the vtable for an element. + pub const fn new<E: NativeElement>( + name: &'static str, + title: &'static str, + docs: &'static str, + fields: &'static [FieldVtable<Packed<E>>], + field_id: fn(name: &str) -> Option<u8>, + capability: fn(TypeId) -> Option<NonNull<()>>, + store: fn() -> &'static LazyElementStore, + ) -> ContentVtable<Packed<E>> { + ContentVtable { + name, + title, + docs, + keywords: &[], + fields, + field_id, + construct: <E as Construct>::construct, + set: <E as Set>::set, + local_name: None, + scope: || Scope::new(), + capability, + drop: RawContent::drop_impl::<E>, + clone: RawContent::clone_impl::<E>, + hash: |elem| typst_utils::hash128(elem.as_ref()), + debug: |elem, f| Debug::fmt(elem.as_ref(), f), + eq: None, + repr: None, + store, + } + } + + /// Retrieves the vtable of the element with the given ID. + pub fn field(&self, id: u8) -> Option<&'static FieldVtable> { + self.fields.get(usize::from(id)) + } +} + +impl<E: NativeElement> ContentVtable<Packed<E>> { + /// Attaches search keywords for the documentation. + pub const fn with_keywords(mut self, keywords: &'static [&'static str]) -> Self { + self.keywords = keywords; + self + } + + /// Takes a [`Repr`] impl into account. + pub const fn with_repr(mut self) -> Self + where + E: Repr, + { + self.repr = Some(|e| E::repr(&**e)); + self + } + + /// Takes a [`PartialEq`] impl into account. + pub const fn with_partial_eq(mut self) -> Self + where + E: PartialEq, + { + self.eq = Some(|a, b| E::eq(&**a, &**b)); + self + } + + /// Takes a [`LocalName`] impl into account. + pub const fn with_local_name(mut self) -> Self + where + Packed<E>: LocalName, + { + self.local_name = Some(<Packed<E> as LocalName>::local_name); + self + } + + /// Takes a [`NativeScope`] impl into account. + pub const fn with_scope(mut self) -> Self + where + E: NativeScope, + { + self.scope = || E::scope(); + self + } + + /// Type-erases the data. + pub const fn erase(self) -> ContentVtable { + // Safety: + // - `ContentVtable` is `repr(C)`. + // - `ContentVtable` does not hold any `E`-specific data except for + // function pointers. + // - All functions pointers have the same memory layout. + // - All functions containing `E` are marked as unsafe and callers need + // to uphold the guarantee that they only call them with raw content + // that is of type `E`. + // - `Packed<E>` and `RawContent` have the exact same memory layout + // because of `repr(transparent)`. + unsafe { + std::mem::transmute::<ContentVtable<Packed<E>>, ContentVtable<RawContent>>( + self, + ) + } + } +} + +impl<T> ContentHandle<T> { + /// Provides safe access to operations for the field with the given `id`. + pub(super) fn field(self, id: u8) -> Option<FieldHandle<T>> { + self.fields.get(usize::from(id)).map(|vtable| { + // Safety: Field vtables are of same type as the content vtable. + unsafe { Handle::new(self.0, vtable) } + }) + } + + /// Provides safe access to all field operations. + pub(super) fn fields(self) -> impl Iterator<Item = FieldHandle<T>> + where + T: Copy, + { + self.fields.iter().map(move |vtable| { + // Safety: Field vtables are of same type as the content vtable. + unsafe { Handle::new(self.0, vtable) } + }) + } +} + +impl ContentHandle<&RawContent> { + /// See [`ContentVtable::debug`]. + pub fn debug(&self, f: &mut Formatter) -> fmt::Result { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.debug)(self.0, f) } + } + + /// See [`ContentVtable::repr`]. + pub fn repr(&self) -> Option<EcoString> { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { self.1.repr.map(|f| f(self.0)) } + } + + /// See [`ContentVtable::clone`]. + pub fn clone(&self) -> RawContent { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.clone)(self.0) } + } + + /// See [`ContentVtable::hash`]. + pub fn hash(&self) -> u128 { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.hash)(self.0) } + } +} + +impl ContentHandle<&mut RawContent> { + /// See [`ContentVtable::drop`]. + pub unsafe fn drop(&mut self) { + // Safety: + // - `Handle` has the invariant that the vtable is matching. + // - The caller satifies the requirements of `drop` + unsafe { (self.1.drop)(self.0) } + } +} + +impl ContentHandle<(&RawContent, &RawContent)> { + /// See [`ContentVtable::eq`]. + pub fn eq(&self) -> Option<bool> { + // Safety: `Handle` has the invariant that the vtable is matching. + let (a, b) = self.0; + unsafe { self.1.eq.map(|f| f(a, b)) } + } +} + +/// A vtable for performing field-specific actions on type-erased +/// content. Also contains general metadata for the specific field. +#[repr(C)] +pub struct FieldVtable<T: 'static = RawContent> { + /// The field's name, as in code. + pub(super) name: &'static str, + /// The fields's documentation (as Markdown). + pub(super) docs: &'static str, + + /// Whether the field's parameter is positional. + pub(super) positional: bool, + /// Whether the field's parameter is variadic. + pub(super) variadic: bool, + /// Whether the field's parameter is required. + pub(super) required: bool, + /// Whether the field can be set via a set rule. + pub(super) settable: bool, + /// Whether the field is synthesized (i.e. initially not present). + pub(super) synthesized: bool, + /// Reflects what types the field's parameter accepts. + pub(super) input: fn() -> CastInfo, + /// Produces the default value of the field, if any. This would e.g. be + /// `None` for a required parameter. + pub(super) default: Option<fn() -> Value>, + + /// Whether the field is set on the given element. Always true for required + /// fields, but can be false for settable or synthesized fields. + pub(super) has: unsafe fn(elem: &T) -> bool, + /// Retrieves the field and [turns it into a + /// value](crate::foundations::IntoValue). + pub(super) get: unsafe fn(elem: &T) -> Option<Value>, + /// Retrieves the field given styles. The resulting value may come from the + /// element, the style chain, or a mix (if it's a + /// [`Fold`](crate::foundations::Fold) field). + pub(super) get_with_styles: unsafe fn(elem: &T, StyleChain) -> Option<Value>, + /// Retrieves the field just from the styles. + pub(super) get_from_styles: fn(StyleChain) -> Option<Value>, + /// Sets the field from the styles if it is currently unset. (Or merges + /// with the style data in case of a `Fold` field). + pub(super) materialize: unsafe fn(elem: &mut T, styles: StyleChain), + /// Compares the field for equality. + pub(super) eq: unsafe fn(a: &T, b: &T) -> bool, +} + +impl FieldHandle<&RawContent> { + /// See [`FieldVtable::has`]. + pub fn has(&self) -> bool { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.has)(self.0) } + } + + /// See [`FieldVtable::get`]. + pub fn get(&self) -> Option<Value> { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.get)(self.0) } + } + + /// See [`FieldVtable::get_with_styles`]. + pub fn get_with_styles(&self, styles: StyleChain) -> Option<Value> { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.get_with_styles)(self.0, styles) } + } +} + +impl FieldHandle<&mut RawContent> { + /// See [`FieldVtable::materialize`]. + pub fn materialize(&mut self, styles: StyleChain) { + // Safety: `Handle` has the invariant that the vtable is matching. + unsafe { (self.1.materialize)(self.0, styles) } + } +} + +impl FieldHandle<(&RawContent, &RawContent)> { + /// See [`FieldVtable::eq`]. + pub fn eq(&self) -> bool { + // Safety: `Handle` has the invariant that the vtable is matching. + let (a, b) = self.0; + unsafe { (self.1.eq)(a, b) } + } +} diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs index 6840f855..382beb2c 100644 --- a/crates/typst-library/src/foundations/mod.rs +++ b/crates/typst-library/src/foundations/mod.rs @@ -17,7 +17,6 @@ mod datetime; mod decimal; mod dict; mod duration; -mod element; mod fields; mod float; mod func; @@ -49,7 +48,6 @@ pub use self::datetime::*; pub use self::decimal::*; pub use self::dict::*; pub use self::duration::*; -pub use self::element::*; pub use self::fields::*; pub use self::float::*; pub use self::func::*; diff --git a/crates/typst-library/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs index e1ce61b8..838584cc 100644 --- a/crates/typst-library/src/foundations/scope.rs +++ b/crates/typst-library/src/foundations/scope.rs @@ -8,8 +8,7 @@ use typst_syntax::Span; use crate::diag::{bail, DeprecationSink, HintedStrResult, HintedString, StrResult}; use crate::foundations::{ - Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType, - Type, Value, + Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType, Value, }; use crate::{Category, Library}; @@ -149,15 +148,15 @@ impl Scope { /// Define a native type. #[track_caller] pub fn define_type<T: NativeType>(&mut self) -> &mut Binding { - let data = T::data(); - self.define(data.name, Type::from(data)) + let ty = T::ty(); + self.define(ty.short_name(), ty) } /// Define a native element. #[track_caller] pub fn define_elem<T: NativeElement>(&mut self) -> &mut Binding { - let data = T::data(); - self.define(data.name, Element::from(data)) + let elem = T::ELEM; + self.define(elem.name(), elem) } /// Define a built-in with compile-time known name and returns a mutable diff --git a/crates/typst-library/src/foundations/selector.rs b/crates/typst-library/src/foundations/selector.rs index bf5449d9..8f264a81 100644 --- a/crates/typst-library/src/foundations/selector.rs +++ b/crates/typst-library/src/foundations/selector.rs @@ -21,12 +21,12 @@ macro_rules! __select_where { let mut fields = ::smallvec::SmallVec::new(); $( fields.push(( - <$ty as $crate::foundations::Fields>::Enum::$field as u8, + <$ty>::$field.index(), $crate::foundations::IntoValue::into_value($value), )); )* $crate::foundations::Selector::Elem( - <$ty as $crate::foundations::NativeElement>::elem(), + <$ty as $crate::foundations::NativeElement>::ELEM, Some(fields), ) }}; diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs index d124f2c8..978b47d5 100644 --- a/crates/typst-library/src/foundations/styles.rs +++ b/crates/typst-library/src/foundations/styles.rs @@ -12,8 +12,8 @@ use typst_utils::LazyHash; use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::engine::Engine; use crate::foundations::{ - cast, ty, Content, Context, Element, Func, NativeElement, OneOrMultiple, Repr, - Selector, + cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple, + RefableProperty, Repr, Selector, SettableProperty, }; use crate::text::{FontFamily, FontList, TextElem}; @@ -48,7 +48,16 @@ impl Styles { /// If the property needs folding and the value is already contained in the /// style map, `self` contributes the outer values and `value` is the inner /// one. - pub fn set(&mut self, style: impl Into<Style>) { + pub fn set<E, const I: u8>(&mut self, field: Field<E, I>, value: E::Type) + where + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, + { + self.push(Property::new(field, value)); + } + + /// Add a new style to the list. + pub fn push(&mut self, style: impl Into<Style>) { self.0.push(LazyHash::new(style.into())); } @@ -101,22 +110,25 @@ impl Styles { } /// Whether there is a style for the given field of the given element. - pub fn has<T: NativeElement>(&self, field: u8) -> bool { - let elem = T::elem(); + pub fn has<E: NativeElement, const I: u8>(&self, _: Field<E, I>) -> bool { + let elem = E::ELEM; self.0 .iter() .filter_map(|style| style.property()) - .any(|property| property.is_of(elem) && property.id == field) + .any(|property| property.is_of(elem) && property.id == I) } /// Set a font family composed of a preferred family and existing families /// from a style chain. pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { - self.set(TextElem::set_font(FontList( - std::iter::once(preferred) - .chain(TextElem::font_in(existing).into_iter().cloned()) - .collect(), - ))); + self.set( + TextElem::font, + FontList( + std::iter::once(preferred) + .chain(existing.get_ref(TextElem::font).into_iter().cloned()) + .collect(), + ), + ); } } @@ -281,14 +293,14 @@ pub struct Property { impl Property { /// Create a new property from a key-value pair. - pub fn new<E, T>(id: u8, value: T) -> Self + pub fn new<E, const I: u8>(_: Field<E, I>, value: E::Type) -> Self where - E: NativeElement, - T: Debug + Clone + Hash + Send + Sync + 'static, + E: SettableProperty<I>, + E::Type: Debug + Clone + Hash + Send + Sync + 'static, { Self { - elem: E::elem(), - id, + elem: E::ELEM, + id: I, value: Block::new(value), span: Span::detached(), liftable: false, @@ -340,8 +352,11 @@ impl Block { } /// Downcasts the block to the specified type. - fn downcast<T: 'static>(&self) -> Option<&T> { - self.0.as_any().downcast_ref() + fn downcast<T: 'static>(&self, func: Element, id: u8) -> &T { + self.0 + .as_any() + .downcast_ref() + .unwrap_or_else(|| block_wrong_type(func, id, self)) } } @@ -528,90 +543,113 @@ impl<'a> StyleChain<'a> { Self { head: &root.0, tail: None } } - /// Make the given chainable the first link of this chain. + /// Retrieves the value of the given field from the style chain. /// - /// The resulting style chain contains styles from `local` as well as - /// `self`. The ones from `local` take precedence over the ones from - /// `self`. For folded properties `local` contributes the inner value. - pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b> + /// A `Field` value is a zero-sized value that specifies which field of an + /// element you want to retrieve on the type-system level. It also ensures + /// that Rust can infer the correct return type. + /// + /// Should be preferred over [`get_cloned`](Self::get_cloned) or + /// [`get_ref`](Self::get_ref), but is only available for [`Copy`] types. + /// For other types an explicit decision needs to be made whether cloning is + /// necessary. + pub fn get<E, const I: u8>(self, field: Field<E, I>) -> E::Type where - C: Chainable + ?Sized, + E: SettableProperty<I>, + E::Type: Copy, { - Chainable::chain(local, self) + self.get_cloned(field) } - /// Cast the first value for the given property in the chain. - pub fn get<T: Clone + 'static>( - self, - func: Element, - id: u8, - inherent: Option<&T>, - default: impl Fn() -> T, - ) -> T { - self.properties::<T>(func, id, inherent) - .next() - .cloned() - .unwrap_or_else(default) + /// Retrieves and clones the value from the style chain. + /// + /// Prefer [`get`](Self::get) if the type is `Copy` and + /// [`get_ref`](Self::get_ref) if a reference suffices. + pub fn get_cloned<E, const I: u8>(self, _: Field<E, I>) -> E::Type + where + E: SettableProperty<I>, + { + if let Some(fold) = E::FOLD { + self.get_folded::<E::Type>(E::ELEM, I, fold, E::default()) + } else { + self.get_unfolded::<E::Type>(E::ELEM, I) + .cloned() + .unwrap_or_else(E::default) + } } - /// Cast the first value for the given property in the chain, - /// returning a borrowed value. - pub fn get_ref<T: 'static>( + /// Retrieves a reference to the value of the given field from the style + /// chain. + /// + /// Not possible if the value needs folding. + pub fn get_ref<E, const I: u8>(self, _: Field<E, I>) -> &'a E::Type + where + E: RefableProperty<I>, + { + self.get_unfolded(E::ELEM, I).unwrap_or_else(|| E::default_ref()) + } + + /// Retrieves the value and then immediately [resolves](Resolve) it. + pub fn resolve<E, const I: u8>( self, - func: Element, - id: u8, - inherent: Option<&'a T>, - default: impl Fn() -> &'a T, - ) -> &'a T { - self.properties::<T>(func, id, inherent) - .next() - .unwrap_or_else(default) + field: Field<E, I>, + ) -> <E::Type as Resolve>::Output + where + E: SettableProperty<I>, + E::Type: Resolve, + { + self.get_cloned(field).resolve(self) + } + + /// Retrieves a reference to a field, also taking into account the + /// instance's value if any. + fn get_unfolded<T: 'static>(self, func: Element, id: u8) -> Option<&'a T> { + self.find(func, id).map(|block| block.downcast(func, id)) } - /// Cast the first value for the given property in the chain, taking - /// `Fold` implementations into account. - pub fn get_folded<T: Fold + Clone + 'static>( + /// Retrieves a reference to a field, also taking into account the + /// instance's value if any. + fn get_folded<T: 'static + Clone>( self, func: Element, id: u8, - inherent: Option<&T>, - default: impl Fn() -> T, + fold: fn(T, T) -> T, + default: T, ) -> T { - fn next<T: Fold>( - mut values: impl Iterator<Item = T>, - default: &impl Fn() -> T, - ) -> T { - values - .next() - .map(|value| value.fold(next(values, default))) - .unwrap_or_else(default) + let iter = self + .properties(func, id) + .map(|block| block.downcast::<T>(func, id).clone()); + + if let Some(folded) = iter.reduce(fold) { + fold(folded, default) + } else { + default } - next(self.properties::<T>(func, id, inherent).cloned(), &default) } /// Iterate over all values for the given property in the chain. - fn properties<T: 'static>( - self, - func: Element, - id: u8, - inherent: Option<&'a T>, - ) -> impl Iterator<Item = &'a T> { - inherent.into_iter().chain( - self.entries() - .filter_map(|style| style.property()) - .filter(move |property| property.is(func, id)) - .map(|property| &property.value) - .map(move |value| { - value.downcast().unwrap_or_else(|| { - panic!( - "attempted to read a value of a different type than was written {}.{}: {:?}", - func.name(), - func.field_name(id).unwrap(), - value - ) - }) - }), - ) + fn find(self, func: Element, id: u8) -> Option<&'a Block> { + self.properties(func, id).next() + } + + /// Iterate over all values for the given property in the chain. + fn properties(self, func: Element, id: u8) -> impl Iterator<Item = &'a Block> { + self.entries() + .filter_map(|style| style.property()) + .filter(move |property| property.is(func, id)) + .map(|property| &property.value) + } + + /// Make the given chainable the first link of this chain. + /// + /// The resulting style chain contains styles from `local` as well as + /// `self`. The ones from `local` take precedence over the ones from + /// `self`. For folded properties `local` contributes the inner value. + pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b> + where + C: Chainable + ?Sized, + { + Chainable::chain(local, self) } /// Iterate over the entries of the chain. @@ -804,6 +842,9 @@ impl<T: Resolve> Resolve for Option<T> { /// #set rect(stroke: 4pt) /// #rect() /// ``` +/// +/// Note: Folding must be associative, i.e. any implementation must satisfy +/// `fold(fold(a, b), c) == fold(a, fold(b, c))`. pub trait Fold { /// Fold this inner value with an outer folded value. fn fold(self, outer: Self) -> Self; @@ -847,6 +888,9 @@ impl<T> Fold for OneOrMultiple<T> { } } +/// A [folding](Fold) function. +pub type FoldFn<T> = fn(T, T) -> T; + /// A variant of fold for foldable optional (`Option<T>`) values where an inner /// `None` value isn't respected (contrary to `Option`'s usual `Fold` /// implementation, with which folding with an inner `None` always returns @@ -884,3 +928,13 @@ impl Fold for Depth { Self(outer.0 + self.0) } } + +#[cold] +fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! { + panic!( + "attempted to read a value of a different type than was written {}.{}: {:?}", + func.name(), + func.field_name(id).unwrap(), + value + ) +} diff --git a/crates/typst-library/src/foundations/target.rs b/crates/typst-library/src/foundations/target.rs index 2a21fd42..71e7554e 100644 --- a/crates/typst-library/src/foundations/target.rs +++ b/crates/typst-library/src/foundations/target.rs @@ -73,5 +73,5 @@ pub struct TargetElem { /// ``` #[func(contextual)] pub fn target(context: Tracked<Context>) -> HintedStrResult<Target> { - Ok(TargetElem::target_in(context.styles()?)) + Ok(context.styles()?.get(TargetElem::target)) } |
