Migrate metadata fields out of individual elements #3200
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR migrates content's metadata fields from being stored in each element individually and accessed via a trait object to being stored directly in the Content itself. This is only an intermediate step in merging the
Content
andValue
types.Motivation
Currently, Typst has two separate kinds of "types". Proper types and element functions. Instances of the former are
Value
s while instances of the latter areContent
. Unifying both would simplify and streamline the language and unlock new capabilities like writing show rules for proper types (datetime, float, etc.). Moreover, it means that we can realize custom elements and custom types through the same mechanism in the future.To be able to merge
Content
andValue
,Value
s need to become interoperable with the styling system, which requires the ability to attach a span, a label, a location and some other metadata to it. Currently, this metadata is stored in each kind of element manually (these fields are generated by the#[elem]
macro). This approach is incompatible with existing values (we wouldn't want to and really couldn't add all these fields to other kinds of values like integers, floats, or colors). Instead, the metadata should live in theContent
/Value
itself. This also unlocks optimizations for moving some of the rare metadata into a separate, optional allocation.However, sometimes we need to interact with an element of a fixed type while still retaining access to its metadata. Currently
&self
of aHeadingElem
provides access to the span. This isn't the case anymore if the span is stored in theContent
rather than theHeadingElem
. To fix this, this PR introduces a newPacked<T>
wrapper that encapsulates an element of well-known type alongside its metadata.Design
Packed<T>
type is introduced that represents an element that is stored asContent
, but is of a known typePacked<T>
is arepr(transparent)
newtype aroundContent
that derefs toT
Content -> T
casts returnOption<&Packed<T>>
,Option<&mut Packed<T>>
andResult<Packed<T>, Content>
Packed<T>
derefs toT
. Here we use some unsafe to skip the check that is already encoded in the type system.Show
,Layout
etc. are now implemented onPacked<T>
instead ofT
so that they retain access to span, location, etc.Notes
Packed<T>
's deref impl. We wouldn't need this unsafe ifPacked<T>
had a different representation thanContent
which held the reference toT
directly. However, that would reduce the flexibility ofPacked<T>
since Content couldn't be coerced into it. We would most probably need three variants ofPacked
for shared ref, mutable ref, and ownedT
. And even then a deref of aPackedRef<'a, T>
to&T
couldn't properly encode the'a
lifetime. Overall, it would be a much greater hassle and the simple repr(transparent) cast isn't that bad.Show for Packed<T>
can only be implemented in the crate wherePacked
is defined because of the orphan rules and the lack of#[fundamental]
on stable. This means it would not be possible to implement a new element outside of thetypst
crate. Still, if that ability should be needed in the future, we could define a decl-macro that makes it simple to define a crate-localPacked
alternative in a downstream crate and add an associatedPacked
type toNativeElement
which specifies which Packed type an element uses. Generic uses ofPacked<T>
would need to be switched to<T as NativeElement>::Packed
(which could also be realized throughPacked
being a type alias to that).Performance
Some quick benchmarks show that performance is mostly unaffected by this change. However, as mentioned before, this unlocks the capability to do some future optimizations to the merged
Value
/Content
representations like:Commits
The first commit contains the mechanical translation of the show rules, while the second one contains the interesting changes.