typst_library/foundations/content/vtable.rs
1//! A custom [vtable] implementation for content.
2//!
3//! This is similar to what is generated by the Rust compiler under the hood
4//! when using trait objects. However, ours has two key advantages:
5//!
6//! - It can store a _slice_ of sub-vtables for field-specific operations.
7//! - It can store not only methods, but also plain data, allowing us to access
8//!   that data without going through dynamic dispatch.
9//!
10//! Because our vtable pointers are backed by `static` variables, we can also
11//! perform checks for element types by comparing raw vtable pointers giving us
12//! `RawContent::is` without dynamic dispatch.
13//!
14//! Overall, the custom vtable gives us just a little more flexibility and
15//! optimizability than using built-in trait objects.
16//!
17//! Note that all vtable methods receive elements of type `Packed<E>`, but some
18//! only perform actions on the `E` itself, with the shared part kept outside of
19//! the vtable (e.g. `hash`), while some perform the full action (e.g. `clone`
20//! as it needs to return new, fully populated raw content). Which one it is, is
21//! documented for each.
22//!
23//! # Safety
24//! This module contains a lot of `unsafe` keywords, but almost all of it is the
25//! same and quite straightforward. All function pointers that operate on a
26//! specific element type are marked as unsafe. In combination with `repr(C)`,
27//! this grants us the ability to safely transmute a `ContentVtable<Packed<E>>`
28//! into a `ContentVtable<RawContent>` (or just short `ContentVtable`). Callers
29//! of functions marked as unsafe have to guarantee that the `ContentVtable` was
30//! transmuted from the same `E` as the RawContent was constructed from. The
31//! `Handle` struct provides a safe access layer, moving the guarantee that the
32//! vtable is matching into a single spot.
33//!
34//! [vtable]: https://en.wikipedia.org/wiki/Virtual_method_table
35
36use std::any::TypeId;
37use std::fmt::{self, Debug, Formatter};
38use std::ops::Deref;
39use std::ptr::NonNull;
40
41use ecow::EcoString;
42
43use super::raw::RawContent;
44use crate::diag::SourceResult;
45use crate::engine::Engine;
46use crate::foundations::{
47    Args, CastInfo, Construct, Content, LazyElementStore, NativeElement, NativeScope,
48    Packed, Repr, Scope, Set, StyleChain, Styles, Value,
49};
50use crate::text::{Lang, LocalName, Region};
51
52/// Encapsulates content and a vtable, granting safe access to vtable operations.
53pub(super) struct Handle<T, V: 'static>(T, &'static V);
54
55impl<T, V> Handle<T, V> {
56    /// Produces a new handle from content and a vtable.
57    ///
58    /// # Safety
59    /// The content and vtable must be matching, i.e. `vtable` must be derived
60    /// from the content's vtable.
61    pub(super) unsafe fn new(content: T, vtable: &'static V) -> Self {
62        Self(content, vtable)
63    }
64}
65
66impl<T, V> Deref for Handle<T, V> {
67    type Target = V;
68
69    fn deref(&self) -> &Self::Target {
70        self.1
71    }
72}
73
74pub(super) type ContentHandle<T> = Handle<T, ContentVtable>;
75pub(super) type FieldHandle<T> = Handle<T, FieldVtable>;
76
77/// A vtable for performing element-specific actions on type-erased content.
78/// Also contains general metadata for the specific element.
79#[repr(C)]
80pub struct ContentVtable<T: 'static = RawContent> {
81    /// The element's normal name, as in code.
82    pub(super) name: &'static str,
83    /// The element's title-cased name.
84    pub(super) title: &'static str,
85    /// The element's documentation (as Markdown).
86    pub(super) docs: &'static str,
87    /// Search keywords for the documentation.
88    pub(super) keywords: &'static [&'static str],
89
90    /// Subvtables for all fields of the element.
91    pub(super) fields: &'static [FieldVtable<T>],
92    /// Determines the ID for a field name. This is a separate function instead
93    /// of searching through `fields` so that Rust can generate optimized code
94    /// for the string matching.
95    pub(super) field_id: fn(name: &str) -> Option<u8>,
96
97    /// The constructor of the element.
98    pub(super) construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>,
99    /// The set rule of the element.
100    pub(super) set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>,
101    /// The element's local name in a specific lang-region pairing.
102    pub(super) local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
103    /// Produces the associated [`Scope`] of the element.
104    pub(super) scope: fn() -> Scope,
105    /// If the `capability` function returns `Some(p)`, then `p` must be a valid
106    /// pointer to a native Rust vtable of `Packed<Self>` w.r.t to the trait `C`
107    /// where `capability` is `TypeId::of::<dyn C>()`.
108    pub(super) capability: fn(capability: TypeId) -> Option<NonNull<()>>,
109
110    /// The `Drop` impl (for the whole raw content). The content must have a
111    /// reference count of zero and may not be used anymore after `drop` was
112    /// called.
113    pub(super) drop: unsafe fn(&mut RawContent),
114    /// The `Clone` impl (for the whole raw content).
115    pub(super) clone: unsafe fn(&T) -> RawContent,
116    /// The `Hash` impl (for just the element).
117    pub(super) hash: unsafe fn(&T) -> u128,
118    /// The `Debug` impl (for just the element).
119    pub(super) debug: unsafe fn(&T, &mut Formatter) -> fmt::Result,
120    /// The `PartialEq` impl (for just the element). If this is `None`,
121    /// field-wise equality checks (via `FieldVtable`) should be performed.
122    pub(super) eq: Option<unsafe fn(&T, &T) -> bool>,
123    /// The `Repr` impl (for just the element). If this is `None`, a generic
124    /// name + fields representation should be produced.
125    pub(super) repr: Option<unsafe fn(&T) -> EcoString>,
126
127    /// Produces a reference to a `static` variable holding a `LazyElementStore`
128    /// that is unique for this element and can be populated with data that is
129    /// somewhat costly to initialize at runtime and shouldn't be initialized
130    /// over and over again. Must be a function rather than a direct reference
131    /// so that we can store the vtable in a `const` without Rust complaining
132    /// about the presence of interior mutability.
133    pub(super) store: fn() -> &'static LazyElementStore,
134}
135
136impl ContentVtable {
137    /// Creates the vtable for an element.
138    pub const fn new<E: NativeElement>(
139        name: &'static str,
140        title: &'static str,
141        docs: &'static str,
142        fields: &'static [FieldVtable<Packed<E>>],
143        field_id: fn(name: &str) -> Option<u8>,
144        capability: fn(TypeId) -> Option<NonNull<()>>,
145        store: fn() -> &'static LazyElementStore,
146    ) -> ContentVtable<Packed<E>> {
147        ContentVtable {
148            name,
149            title,
150            docs,
151            keywords: &[],
152            fields,
153            field_id,
154            construct: <E as Construct>::construct,
155            set: <E as Set>::set,
156            local_name: None,
157            scope: || Scope::new(),
158            capability,
159            drop: RawContent::drop_impl::<E>,
160            clone: RawContent::clone_impl::<E>,
161            hash: |elem| typst_utils::hash128(elem.as_ref()),
162            debug: |elem, f| Debug::fmt(elem.as_ref(), f),
163            eq: None,
164            repr: None,
165            store,
166        }
167    }
168
169    /// Retrieves the vtable of the element with the given ID.
170    pub fn field(&self, id: u8) -> Option<&'static FieldVtable> {
171        self.fields.get(usize::from(id))
172    }
173}
174
175impl<E: NativeElement> ContentVtable<Packed<E>> {
176    /// Attaches search keywords for the documentation.
177    pub const fn with_keywords(mut self, keywords: &'static [&'static str]) -> Self {
178        self.keywords = keywords;
179        self
180    }
181
182    /// Takes a [`Repr`] impl into account.
183    pub const fn with_repr(mut self) -> Self
184    where
185        E: Repr,
186    {
187        self.repr = Some(|e| E::repr(&**e));
188        self
189    }
190
191    /// Takes a [`PartialEq`] impl into account.
192    pub const fn with_partial_eq(mut self) -> Self
193    where
194        E: PartialEq,
195    {
196        self.eq = Some(|a, b| E::eq(&**a, &**b));
197        self
198    }
199
200    /// Takes a [`LocalName`] impl into account.
201    pub const fn with_local_name(mut self) -> Self
202    where
203        Packed<E>: LocalName,
204    {
205        self.local_name = Some(<Packed<E> as LocalName>::local_name);
206        self
207    }
208
209    /// Takes a [`NativeScope`] impl into account.
210    pub const fn with_scope(mut self) -> Self
211    where
212        E: NativeScope,
213    {
214        self.scope = || E::scope();
215        self
216    }
217
218    /// Type-erases the data.
219    pub const fn erase(self) -> ContentVtable {
220        // Safety:
221        // - `ContentVtable` is `repr(C)`.
222        // - `ContentVtable` does not hold any `E`-specific data except for
223        //   function pointers.
224        // - All functions pointers have the same memory layout.
225        // - All functions containing `E` are marked as unsafe and callers need
226        //   to uphold the guarantee that they only call them with raw content
227        //   that is of type `E`.
228        // - `Packed<E>` and `RawContent` have the exact same memory layout
229        //   because of `repr(transparent)`.
230        unsafe {
231            std::mem::transmute::<ContentVtable<Packed<E>>, ContentVtable<RawContent>>(
232                self,
233            )
234        }
235    }
236}
237
238impl<T> ContentHandle<T> {
239    /// Provides safe access to operations for the field with the given `id`.
240    pub(super) fn field(self, id: u8) -> Option<FieldHandle<T>> {
241        self.fields.get(usize::from(id)).map(|vtable| {
242            // Safety: Field vtables are of same type as the content vtable.
243            unsafe { Handle::new(self.0, vtable) }
244        })
245    }
246
247    /// Provides safe access to all field operations.
248    pub(super) fn fields(self) -> impl Iterator<Item = FieldHandle<T>>
249    where
250        T: Copy,
251    {
252        self.fields.iter().map(move |vtable| {
253            // Safety: Field vtables are of same type as the content vtable.
254            unsafe { Handle::new(self.0, vtable) }
255        })
256    }
257}
258
259impl ContentHandle<&RawContent> {
260    /// See [`ContentVtable::debug`].
261    pub fn debug(&self, f: &mut Formatter) -> fmt::Result {
262        // Safety: `Handle` has the invariant that the vtable is matching.
263        unsafe { (self.1.debug)(self.0, f) }
264    }
265
266    /// See [`ContentVtable::repr`].
267    pub fn repr(&self) -> Option<EcoString> {
268        // Safety: `Handle` has the invariant that the vtable is matching.
269        unsafe { self.1.repr.map(|f| f(self.0)) }
270    }
271
272    /// See [`ContentVtable::clone`].
273    pub fn clone(&self) -> RawContent {
274        // Safety: `Handle` has the invariant that the vtable is matching.
275        unsafe { (self.1.clone)(self.0) }
276    }
277
278    /// See [`ContentVtable::hash`].
279    pub fn hash(&self) -> u128 {
280        // Safety: `Handle` has the invariant that the vtable is matching.
281        unsafe { (self.1.hash)(self.0) }
282    }
283}
284
285impl ContentHandle<&mut RawContent> {
286    /// See [`ContentVtable::drop`].
287    pub unsafe fn drop(&mut self) {
288        // Safety:
289        // - `Handle` has the invariant that the vtable is matching.
290        // - The caller satisfies the requirements of `drop`
291        unsafe { (self.1.drop)(self.0) }
292    }
293}
294
295impl ContentHandle<(&RawContent, &RawContent)> {
296    /// See [`ContentVtable::eq`].
297    pub fn eq(&self) -> Option<bool> {
298        // Safety: `Handle` has the invariant that the vtable is matching.
299        let (a, b) = self.0;
300        unsafe { self.1.eq.map(|f| f(a, b)) }
301    }
302}
303
304/// A vtable for performing field-specific actions on type-erased
305/// content. Also contains general metadata for the specific field.
306#[repr(C)]
307pub struct FieldVtable<T: 'static = RawContent> {
308    /// The field's name, as in code.
309    pub(super) name: &'static str,
310    /// The fields's documentation (as Markdown).
311    pub(super) docs: &'static str,
312
313    /// Whether the field's parameter is positional.
314    pub(super) positional: bool,
315    /// Whether the field's parameter is variadic.
316    pub(super) variadic: bool,
317    /// Whether the field's parameter is required.
318    pub(super) required: bool,
319    /// Whether the field can be set via a set rule.
320    pub(super) settable: bool,
321    /// Whether the field is synthesized (i.e. initially not present).
322    pub(super) synthesized: bool,
323    /// Reflects what types the field's parameter accepts.
324    pub(super) input: fn() -> CastInfo,
325    /// Produces the default value of the field, if any. This would e.g. be
326    /// `None` for a required parameter.
327    pub(super) default: Option<fn() -> Value>,
328
329    /// Whether the field is set on the given element. Always true for required
330    /// fields, but can be false for settable or synthesized fields.
331    pub(super) has: unsafe fn(elem: &T) -> bool,
332    /// Retrieves the field and [turns it into a
333    /// value](crate::foundations::IntoValue).
334    pub(super) get: unsafe fn(elem: &T) -> Option<Value>,
335    /// Retrieves the field given styles. The resulting value may come from the
336    /// element, the style chain, or a mix (if it's a
337    /// [`Fold`](crate::foundations::Fold) field).
338    pub(super) get_with_styles: unsafe fn(elem: &T, StyleChain) -> Option<Value>,
339    /// Retrieves the field just from the styles.
340    pub(super) get_from_styles: fn(StyleChain) -> Option<Value>,
341    /// Sets the field from the styles if it is currently unset. (Or merges
342    /// with the style data in case of a `Fold` field).
343    pub(super) materialize: unsafe fn(elem: &mut T, styles: StyleChain),
344    /// Compares the field for equality.
345    pub(super) eq: unsafe fn(a: &T, b: &T) -> bool,
346}
347
348impl FieldHandle<&RawContent> {
349    /// See [`FieldVtable::has`].
350    pub fn has(&self) -> bool {
351        // Safety: `Handle` has the invariant that the vtable is matching.
352        unsafe { (self.1.has)(self.0) }
353    }
354
355    /// See [`FieldVtable::get`].
356    pub fn get(&self) -> Option<Value> {
357        // Safety: `Handle` has the invariant that the vtable is matching.
358        unsafe { (self.1.get)(self.0) }
359    }
360
361    /// See [`FieldVtable::get_with_styles`].
362    pub fn get_with_styles(&self, styles: StyleChain) -> Option<Value> {
363        // Safety: `Handle` has the invariant that the vtable is matching.
364        unsafe { (self.1.get_with_styles)(self.0, styles) }
365    }
366}
367
368impl FieldHandle<&mut RawContent> {
369    /// See [`FieldVtable::materialize`].
370    pub fn materialize(&mut self, styles: StyleChain) {
371        // Safety: `Handle` has the invariant that the vtable is matching.
372        unsafe { (self.1.materialize)(self.0, styles) }
373    }
374}
375
376impl FieldHandle<(&RawContent, &RawContent)> {
377    /// See [`FieldVtable::eq`].
378    pub fn eq(&self) -> bool {
379        // Safety: `Handle` has the invariant that the vtable is matching.
380        let (a, b) = self.0;
381        unsafe { (self.1.eq)(a, b) }
382    }
383}