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}