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