New #[elem] implementation

This commit is contained in:
Laurenz 2025-07-02 18:33:05 +02:00
parent 2f401f26e1
commit 533f17c28b
5 changed files with 1105 additions and 761 deletions

View File

@ -15,9 +15,9 @@ use typst_utils::{fat, singleton, LazyHash, SmallBitSet};
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,
elem, func, repr, scope, ty, Context, Dict, Element, Field, IntoValue, Label,
NativeElement, Property, Recipe, RecipeIndex, Repr, Selector, SettableProperty, Str,
Style, StyleChain, Styles, Value,
};
use crate::introspection::Location;
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
@ -372,6 +372,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>() {
@ -636,7 +645,18 @@ impl PartialEq for Content {
impl Repr for Content {
fn repr(&self) -> EcoString {
self.inner.elem.repr()
self.inner.elem.repr().unwrap_or_else(|| {
let fields = self
.fields()
.into_iter()
.map(|(name, value)| eco_format!("{}: {}", name, value.repr()))
.collect::<Vec<_>>();
eco_format!(
"{}{}",
self.elem().name(),
repr::pretty_array_like(&fields, false),
)
})
}
}
@ -718,15 +738,29 @@ impl Serialize for Content {
}
/// The trait that combines all the other traits into a trait object.
trait Bounds: Debug + Repr + Fields + Send + Sync + 'static {
trait Bounds: Debug + 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;
fn has(&self, id: u8) -> bool;
fn field(&self, id: u8) -> Result<Value, FieldAccessError>;
fn field_with_styles(
&self,
id: u8,
styles: StyleChain,
) -> Result<Value, FieldAccessError>;
fn materialize(&mut self, styles: StyleChain);
fn fields(&self) -> Dict;
fn repr(&self) -> Option<EcoString>;
}
impl<T: NativeElement> Bounds for T {
impl<T> Bounds for T
where
T: NativeElement,
{
fn dyn_type_id(&self) -> TypeId {
TypeId::of::<Self>()
}
@ -756,7 +790,60 @@ impl<T: NativeElement> Bounds for T {
let Some(other) = other.to_packed::<Self>() else {
return false;
};
*self == **other
if let Some(f) = T::ELEMENT.eq {
f(self, other)
} else {
T::ELEMENT.fields.iter().all(|field| (field.eq)(self, other))
}
}
fn has(&self, id: u8) -> bool {
match T::ELEMENT.get(id) {
Some(data) => (data.has)(self),
None => false,
}
}
fn field(&self, id: u8) -> Result<Value, FieldAccessError> {
match T::ELEMENT.get(id) {
Some(data) => (data.get)(self).ok_or(FieldAccessError::Unset),
None => Err(FieldAccessError::Unknown),
}
}
fn field_with_styles(
&self,
id: u8,
styles: StyleChain,
) -> Result<Value, FieldAccessError> {
match T::ELEMENT.get(id) {
Some(data) => {
(data.get_with_styles)(self, styles).ok_or(FieldAccessError::Unset)
}
None => Err(FieldAccessError::Unknown),
}
}
fn materialize(&mut self, styles: StyleChain) {
for field in T::ELEMENT.fields {
(field.materialize)(self, styles);
}
}
fn fields(&self) -> Dict {
let mut dict = Dict::new();
for field in T::ELEMENT.fields {
if let Some(value) = (field.get)(self) {
dict.insert(field.name.into(), value);
}
}
dict
}
fn repr(&self) -> Option<EcoString> {
// Returns None if the element doesn't have a special `Repr` impl.
// Then, we use a generic one based on `Self::fields`.
T::ELEMENT.repr.map(|f| f(self))
}
}
@ -767,7 +854,7 @@ impl Hash for dyn Bounds {
}
/// A packed element of a static type.
#[derive(Clone, PartialEq, Hash)]
#[derive(Clone)]
#[repr(transparent)]
pub struct Packed<T: NativeElement>(
/// Invariant: Must be of type `T`.
@ -899,8 +986,20 @@ impl<T: NativeElement + Debug> Debug for Packed<T> {
}
}
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);
}
}
/// A sequence of content.
#[elem(Debug, Repr, PartialEq)]
#[elem(Debug, Repr)]
pub struct SequenceElem {
/// The elements.
#[required]
@ -922,19 +1021,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)
@ -985,7 +1078,6 @@ pub trait PlainText {
pub enum FieldAccessError {
Unknown,
Unset,
Internal,
}
impl FieldAccessError {
@ -1003,12 +1095,6 @@ impl FieldAccessError {
field.repr()
)
}
FieldAccessError::Internal => {
eco_format!(
"internal error when accessing field {} in {elem_name} this is a bug",
field.repr()
)
}
}
}

View File

@ -2,8 +2,9 @@ use std::any::TypeId;
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use std::hash::Hash;
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::sync::LazyLock;
use std::sync::{LazyLock, OnceLock};
use ecow::EcoString;
use smallvec::SmallVec;
@ -14,10 +15,11 @@ 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,
cast, Args, CastInfo, Container, Content, FieldAccessError, Fold, Func, IntoValue,
NativeScope, Packed, ParamInfo, Property, Reflect, Repr, Resolve, Scope, Selector,
StyleChain, Styles, Value,
};
use crate::text::{Lang, Region};
use crate::text::{Lang, LocalName, Region};
/// A document element.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
@ -169,19 +171,15 @@ cast! {
/// 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
Debug + Clone + Hash + Construct + Set + Capable + Send + Sync + 'static
{
const ELEMENT: TypedElementData<Self>;
/// Get the element data for the native Rust element.
fn data() -> &'static NativeElementData
where
Self: Sized;
/// Get the element for the native Rust element.
fn elem() -> Element
where
@ -197,11 +195,12 @@ pub trait NativeElement:
{
Content::new(self)
}
}
/// Get the element data for the native Rust element.
fn data() -> &'static NativeElementData
where
Self: Sized;
impl<T: NativeElement> IntoValue for T {
fn into_value(self) -> Value {
Value::Content(self.pack())
}
}
/// Used to cast an element to a trait object for a trait it implements.
@ -215,38 +214,6 @@ pub unsafe trait Capable {
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);
/// Get the fields of the element.
fn fields(&self) -> Dict;
}
/// An element's constructor function.
pub trait Construct {
/// Construct an element from the arguments.
@ -266,48 +233,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 +256,697 @@ pub trait ShowSet {
/// that should work even in the face of a user-defined show rule.
fn show_set(&self, styles: StyleChain) -> Styles;
}
/// Type-erased metadata and routines for 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.
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>,
/// Associated definitions of the element.
pub scope: LazyLock<Scope>,
/// A list of parameter information for each field.
pub params: LazyLock<Vec<ParamInfo>>,
}
impl NativeElementData {
/// Creates type-erased element data for the given element.
pub const fn new<E>() -> Self
where
E: NativeElement,
{
Self {
name: E::ELEMENT.name,
title: E::ELEMENT.title,
docs: E::ELEMENT.docs,
keywords: E::ELEMENT.keywords,
local_name: E::ELEMENT.local_name,
field_from_styles: |i, styles| match E::ELEMENT.get(i) {
Some(field) => {
(field.get_from_styles)(styles).ok_or(FieldAccessError::Unknown)
}
None => Err(FieldAccessError::Unknown),
},
field_id: E::ELEMENT.field_id,
field_name: |i| E::ELEMENT.get(i).map(|data| data.name),
construct: <E as Construct>::construct,
set: <E as Set>::set,
vtable: <E as Capable>::vtable,
scope: LazyLock::new(E::ELEMENT.scope),
params: LazyLock::new(|| {
E::ELEMENT
.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()
}),
}
}
}
impl From<&'static NativeElementData> for Element {
fn from(data: &'static NativeElementData) -> Self {
Self(Static(data))
}
}
cast! {
&'static NativeElementData,
self => Element::from(self).into_value(),
}
/// Type-aware metadata and routines for a native element.
pub struct TypedElementData<E: NativeElement> {
pub name: &'static str,
pub title: &'static str,
pub docs: &'static str,
pub keywords: &'static [&'static str],
pub fields: &'static [TypedFieldData<E>],
pub field_id: fn(name: &str) -> Option<u8>,
pub repr: Option<fn(&E) -> EcoString>,
pub eq: Option<fn(&E, &E) -> bool>,
pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
pub scope: fn() -> Scope,
}
impl<E: NativeElement> TypedElementData<E> {
/// Creates the data from its parts. This is called in the `#[elem]` macro.
pub const fn new(
name: &'static str,
title: &'static str,
docs: &'static str,
fields: &'static [TypedFieldData<E>],
field_id: fn(name: &str) -> Option<u8>,
) -> Self {
Self {
name,
title,
docs,
keywords: &[],
fields,
field_id,
repr: None,
eq: None,
local_name: None,
scope: || Scope::new(),
}
}
/// Attaches search keywords for the documentation.
pub const fn with_keywords(self, keywords: &'static [&'static str]) -> Self {
Self { keywords, ..self }
}
/// Takes a `Repr` impl into account.
pub const fn with_repr(self) -> Self
where
E: Repr,
{
Self { repr: Some(E::repr), ..self }
}
/// Takes a `PartialEq` impl into account.
pub const fn with_partial_eq(self) -> Self
where
E: PartialEq,
{
Self { eq: Some(E::eq), ..self }
}
/// Takes a `LocalName` impl into account.
pub const fn with_local_name(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(self) -> Self
where
E: NativeScope,
{
Self { scope: || E::scope(), ..self }
}
/// Retrieves the field with the given index.
pub fn get(&self, i: u8) -> Option<&'static TypedFieldData<E>> {
self.fields.get(usize::from(i))
}
}
/// Metadata for a field and routines that is aware of concrete element, but
/// abstracts over what kind of field it is (required / variadic / synthesized /
/// settable / ghost).
pub struct TypedFieldData<E: NativeElement> {
pub name: &'static str,
pub docs: &'static str,
pub positional: bool,
pub variadic: bool,
pub required: bool,
pub settable: bool,
pub synthesized: bool,
pub input: fn() -> CastInfo,
pub default: Option<fn() -> Value>,
pub has: fn(content: &E) -> bool,
pub get: fn(content: &E) -> Option<Value>,
pub get_with_styles: fn(content: &E, StyleChain) -> Option<Value>,
pub get_from_styles: fn(StyleChain) -> Option<Value>,
pub materialize: fn(content: &mut E, styles: StyleChain),
pub eq: fn(a: &E, b: &E) -> bool,
}
impl<E: NativeElement> TypedFieldData<E> {
/// Creates type-erased metadata and routines for a `#[required]` field.
pub const fn required<const I: u8>() -> Self
where
E: RequiredField<I>,
E::Type: Reflect + IntoValue + PartialEq,
{
Self {
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 type-erased metadata and routines for a `#[variadic]` field.
pub const fn variadic<const I: u8>() -> Self
where
E: RequiredField<I>,
E::Type: Container + IntoValue + PartialEq,
<E::Type as Container>::Inner: Reflect,
{
Self {
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),
}
}
/// Creates type-erased metadata and routines for a `#[synthesized]` field.
pub const fn synthesized<const I: u8>() -> Self
where
E: SynthesizedField<I>,
E::Type: Reflect + IntoValue + PartialEq,
{
Self {
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,
}
}
/// Creates type-erased metadata and routines for a normal settable field.
pub const fn settable<const I: u8>() -> Self
where
E: SettableField<I>,
E::Type: Reflect + IntoValue + PartialEq,
{
Self {
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::PROPERTY.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(styles).into_value())
},
get_from_styles: |styles| Some(styles.get::<E, I>(Field::new()).into_value()),
materialize: |elem, styles| {
if !(E::FIELD.get)(elem).is_set() {
(E::FIELD.get_mut)(elem).set(styles.get::<E, I>(Field::new()));
}
},
eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(),
}
}
/// Creates type-erased metadata and routines for a `#[ghost]` field.
pub const fn ghost<const I: u8>() -> Self
where
E: SettableProperty<I>,
E::Type: Reflect + IntoValue + PartialEq,
{
Self {
name: E::PROPERTY.name,
docs: E::PROPERTY.docs,
positional: E::PROPERTY.positional,
required: false,
variadic: false,
settable: true,
synthesized: false,
input: || <E::Type as Reflect>::input(),
default: Some(|| E::PROPERTY.default().into_value()),
has: |_| false,
get: |_| None,
get_with_styles: |_, styles| {
Some(styles.get::<E, I>(Field::new()).into_value())
},
get_from_styles: |styles| Some(styles.get::<E, I>(Field::new()).into_value()),
materialize: |_, _| {},
eq: |_, _| true,
}
}
/// Creates type-erased metadata and routines for an `#[external]` field.
pub const fn external<const I: u8>() -> Self
where
E: ExternalField<I>,
E::Type: Reflect + IntoValue,
{
Self {
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 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> {
pub name: &'static str,
pub docs: &'static str,
pub get: fn(&E) -> &E::Type,
pub get_mut: fn(&mut E) -> &mut 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,
get_mut: fn(&mut E) -> &mut E::Type,
) -> Self {
Self { name, docs, get, get_mut }
}
}
/// A field that is initially unset, but may be set through a [`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> {
pub name: &'static str,
pub docs: &'static str,
pub get: fn(&E) -> &Option<E::Type>,
pub get_mut: fn(&mut E) -> &mut 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>,
get_mut: fn(&mut E) -> &mut Option<E::Type>,
) -> Self {
Self { name, docs, get, get_mut }
}
}
/// 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> {
pub get: fn(&E) -> &Settable<E, I>,
pub get_mut: fn(&mut E) -> &mut Settable<E, I>,
pub 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
}
}
/// 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 PROPERTY: SettablePropertyData<Self, I>;
}
impl<T, const I: u8> SettableProperty<I> for T
where
T: SettableField<I>,
{
type Type = <Self as SettableField<I>>::Type;
const PROPERTY: SettablePropertyData<Self, I> =
<Self as SettableField<I>>::FIELD.property;
}
/// Metadata and routines for a [`SettableProperty`].
pub struct SettablePropertyData<E: SettableProperty<I>, const I: u8> {
pub name: &'static str,
pub docs: &'static str,
pub positional: bool,
pub default: fn() -> E::Type,
pub slot: fn() -> &'static OnceLock<E::Type>,
#[allow(clippy::type_complexity)]
pub fold: Option<fn(E::Type, E::Type) -> 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 }
}
/// Produces an instance of the property's default value.
pub fn default(&self) -> E::Type
where
E::Type: Clone,
{
// Avoid recreating an expensive instance over and over, but also
// avoid unnecessary lazy initialization for cheap types.
if std::mem::needs_drop::<E::Type>() {
self.default_ref().clone()
} else {
(self.default)()
}
}
/// Produces a static reference to this property's default value.
pub fn default_ref(&self) -> &'static E::Type {
(self.slot)().get_or_init(self.default)
}
}
/// 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.
#[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 or if it needs folding.
pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type
where
E::Type: Clone,
{
styles.get_with::<E, I>(Field::new(), self.0.as_ref())
}
/// Retrieves a reference to the value given styles. Not possible if the
/// value needs folding.
pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type
where
E: RefableProperty<I>,
{
styles.get_ref_with::<E, I>(Field::new(), self.0.as_ref())
}
/// 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 + Clone,
{
self.get(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 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`] 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 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> {
pub name: &'static str,
pub docs: &'static str,
pub 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 }
}
}

View File

@ -21,7 +21,7 @@ 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),
));
)*

View File

@ -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,12 +110,12 @@ impl Styles {
}
/// Whether there is a style for the given field of the given element.
pub fn has<T: NativeElement>(&self, field: u8) -> bool {
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
@ -284,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,
id: I,
value: Block::new(value),
span: Span::detached(),
liftable: false,
@ -543,53 +552,79 @@ impl<'a> StyleChain<'a> {
Chainable::chain(local, self)
}
/// 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 the value of the given field from the style chain.
///
/// 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.
pub fn get<E, const I: u8>(self, field: Field<E, I>) -> E::Type
where
E: SettableProperty<I>,
{
self.get_with(field, None)
}
/// Cast the first value for the given property in the chain,
/// returning a borrowed value.
pub fn get_ref<T: 'static>(
/// 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(field).resolve(self)
}
/// 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 the value of the given field from the style
/// chain.
pub fn get_ref<E, const I: u8>(self, field: Field<E, I>) -> &'a E::Type
where
E: RefableProperty<I>,
{
self.get_ref_with(field, None)
}
/// Retrieves a field, also taking into account the instance's value if any.
pub(crate) fn get_with<E, const I: u8>(
self,
func: Element,
id: u8,
inherent: Option<&T>,
default: impl Fn() -> 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)
_: Field<E, I>,
inherent: Option<&'a E::Type>,
) -> E::Type
where
E: SettableProperty<I>,
{
let mut properties = self.properties::<E::Type>(E::elem(), I, inherent);
if let Some(fold) = E::PROPERTY.fold {
fn next<T>(
mut values: impl Iterator<Item = T>,
default: fn() -> T,
fold: fn(T, T) -> T,
) -> T {
values
.next()
.map(|value| fold(value, next(values, default, fold)))
.unwrap_or_else(default)
}
next(properties.cloned(), || E::PROPERTY.default(), fold)
} else {
properties.next().cloned().unwrap_or_else(|| E::PROPERTY.default())
}
next(self.properties::<T>(func, id, inherent).cloned(), &default)
}
/// Retrieves a reference to a field, also taking into account the
/// instance's value if any.
pub(crate) fn get_ref_with<E, const I: u8>(
self,
_: Field<E, I>,
inherent: Option<&'a E::Type>,
) -> &'a E::Type
where
E: RefableProperty<I>,
{
self.properties::<E::Type>(E::elem(), I, inherent)
.next()
.unwrap_or_else(|| E::PROPERTY.default_ref())
}
/// Iterate over all values for the given property in the chain.

View File

@ -1,9 +1,9 @@
use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase};
use heck::ToKebabCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parse_quote, Ident, Result, Token};
use syn::{Ident, Result, Token};
use crate::util::{
determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr,
@ -39,14 +39,13 @@ struct Elem {
}
impl Elem {
/// Calls the closure to produce a token stream if the
/// element has the given capability.
/// Whether the element has the given trait listed as a capability.
fn can(&self, name: &str) -> bool {
self.capabilities.iter().any(|capability| capability == name)
}
/// Calls the closure to produce a token stream if the
/// element does not have the given capability.
/// Whether the element does not have the given trait listed as a
/// capability.
fn cannot(&self, name: &str) -> bool {
!self.can(name)
}
@ -68,21 +67,6 @@ impl Elem {
self.struct_fields().filter(|field| !field.required)
}
/// Fields that are relevant for equality.
///
/// Synthesized fields are excluded to ensure equality before and after
/// synthesis.
fn eq_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.struct_fields().filter(|field| !field.synthesized)
}
/// Fields that show up in the documentation.
fn doc_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.fields
.iter()
.filter(|field| !field.internal && !field.synthesized)
}
/// Fields that are relevant for `Construct` impl.
///
/// The reason why fields that are `parse` and internal are allowed is
@ -98,44 +82,20 @@ impl Elem {
fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.construct_fields().filter(|field| !field.required)
}
/// Fields that can be accessed from the style chain.
fn style_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.real_fields()
.filter(|field| !field.required && !field.synthesized)
}
/// Fields that are visible to the user.
fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.real_fields().filter(|field| !field.internal)
}
}
/// A field of an [element definition][`Elem`].
struct Field {
/// The index of the field among all.
i: u8,
/// The name of this field.
ident: Ident,
/// The identifier `{ident}_in`.
ident_in: Ident,
/// The identifier `with_{ident}`.
with_ident: Ident,
/// The identifier `push_{ident}`.
push_ident: Ident,
/// The identifier `set_{ident}`.
set_ident: Ident,
/// The upper camel-case version of `ident`, used for the enum variant name.
enum_ident: Ident,
/// The all-caps snake-case version of `ident`, used for the constant name.
const_ident: Ident,
/// The visibility of this field.
vis: syn::Visibility,
/// The type of this field.
ty: syn::Type,
/// The type returned by accessor methods for this field.
///
/// Usually, this is the same as `ty`, but this might be different
/// if this field has a `#[resolve]` attribute.
output: syn::Type,
/// The field's identifier as exposed to Typst.
name: String,
/// The documentation for this field as a string.
@ -147,16 +107,12 @@ struct Field {
/// Whether this field is variadic; that is, has its values
/// taken from a variable number of arguments.
variadic: bool,
/// Whether this field has a `#[resolve]` attribute.
resolve: bool,
/// Whether this field has a `#[fold]` attribute.
fold: bool,
/// Whether this field is excluded from documentation.
internal: bool,
/// Whether this field exists only in documentation.
external: bool,
/// Whether this field has a `#[borrowed]` attribute.
borrowed: bool,
/// Whether this field has a `#[ghost]` attribute.
ghost: bool,
/// Whether this field has a `#[synthesized]` attribute.
@ -206,7 +162,12 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
bail!(body, "expected named fields");
};
let fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?;
let mut fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?;
fields.sort_by_key(|field| field.internal);
for (i, field) in fields.iter_mut().enumerate() {
field.i = i as u8;
}
if fields.iter().any(|field| field.ghost && !field.internal)
&& meta.capabilities.iter().all(|capability| capability != "Construct")
{
@ -243,27 +204,20 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
let required = has_attr(&mut attrs, "required") || variadic;
let positional = has_attr(&mut attrs, "positional") || required;
let mut field = Field {
let field = Field {
i: 0,
ident: ident.clone(),
ident_in: format_ident!("{ident}_in"),
with_ident: format_ident!("with_{ident}"),
push_ident: format_ident!("push_{ident}"),
set_ident: format_ident!("set_{ident}"),
enum_ident: Ident::new(&ident.to_string().to_upper_camel_case(), ident.span()),
const_ident: Ident::new(&ident.to_string().to_shouty_snake_case(), ident.span()),
vis: field.vis.clone(),
ty: field.ty.clone(),
output: field.ty.clone(),
name: ident.to_string().to_kebab_case(),
docs: documentation(&attrs),
positional,
required,
variadic,
resolve: has_attr(&mut attrs, "resolve"),
fold: has_attr(&mut attrs, "fold"),
internal: has_attr(&mut attrs, "internal"),
external: has_attr(&mut attrs, "external"),
borrowed: has_attr(&mut attrs, "borrowed"),
ghost: has_attr(&mut attrs, "ghost"),
synthesized: has_attr(&mut attrs, "synthesized"),
parse: parse_attr(&mut attrs, "parse")?.flatten(),
@ -275,17 +229,9 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
}
if (field.required || field.synthesized)
&& (field.default.is_some() || field.fold || field.resolve || field.ghost)
&& (field.default.is_some() || field.fold || field.ghost)
{
bail!(
ident,
"required and synthesized fields cannot be default, fold, resolve, or ghost"
);
}
if field.resolve {
let ty = &field.ty;
field.output = parse_quote! { <#ty as #foundations::Resolve>::Output };
bail!(ident, "required and synthesized fields cannot be default, fold, or ghost");
}
validate_attrs(&attrs)?;
@ -297,30 +243,18 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
fn create(element: &Elem) -> Result<TokenStream> {
// The struct itself.
let struct_ = create_struct(element);
// Implementations.
let inherent_impl = create_inherent_impl(element);
// The enum with the struct's fields.
let fields_enum = create_fields_enum(element);
// The statics with borrowed fields' default values.
let default_statics = element
.style_fields()
.filter(|field| field.borrowed)
.map(create_default_static);
// Trait implementations.
let native_element_impl = create_native_elem_impl(element);
let partial_eq_impl =
element.cannot("PartialEq").then(|| create_partial_eq_impl(element));
let field_impls =
element.fields.iter().map(|field| create_field_impl(element, field));
let capable_impl = create_capable_impl(element);
let construct_impl =
element.cannot("Construct").then(|| create_construct_impl(element));
let set_impl = element.cannot("Set").then(|| create_set_impl(element));
let capable_impl = create_capable_impl(element);
let fields_impl = create_fields_impl(element);
let repr_impl = element.cannot("Repr").then(|| create_repr_impl(element));
let locatable_impl = element.can("Locatable").then(|| create_locatable_impl(element));
let mathy_impl = element.can("Mathy").then(|| create_mathy_impl(element));
let into_value_impl = create_into_value_impl(element);
// We use a const block to create an anonymous scope, as to not leak any
// local definitions.
@ -328,19 +262,14 @@ fn create(element: &Elem) -> Result<TokenStream> {
#struct_
const _: () = {
#fields_enum
#(#default_statics)*
#inherent_impl
#native_element_impl
#fields_impl
#(#field_impls)*
#capable_impl
#construct_impl
#set_impl
#partial_eq_impl
#repr_impl
#locatable_impl
#mathy_impl
#into_value_impl
};
})
}
@ -365,80 +294,13 @@ fn create_struct(element: &Elem) -> TokenStream {
/// Create a field declaration for the struct.
fn create_field(field: &Field) -> TokenStream {
let Field { vis, ident, ty, .. } = field;
let Field { i, vis, ident, ty, .. } = field;
if field.required {
quote! { #vis #ident: #ty }
} else if field.synthesized {
quote! { #vis #ident: ::std::option::Option<#ty> }
} else {
quote! { #ident: ::std::option::Option<#ty> }
}
}
/// Creates the element's enum for field identifiers.
fn create_fields_enum(element: &Elem) -> TokenStream {
let variants: Vec<_> = element.real_fields().map(|field| &field.enum_ident).collect();
let names: Vec<_> = element.real_fields().map(|field| &field.name).collect();
let consts: Vec<_> = element.real_fields().map(|field| &field.const_ident).collect();
let repr = (!variants.is_empty()).then(|| quote! { #[repr(u8)] });
quote! {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#repr
pub enum Fields {
#(#variants,)*
}
impl Fields {
/// Converts the field identifier to the field name.
pub fn to_str(self) -> &'static str {
match self {
#(Self::#variants => #names,)*
}
}
}
impl ::std::convert::TryFrom<u8> for Fields {
type Error = #foundations::FieldAccessError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
#(const #consts: u8 = Fields::#variants as u8;)*
match value {
#(#consts => Ok(Self::#variants),)*
_ => Err(#foundations::FieldAccessError::Internal),
}
}
}
impl ::std::str::FromStr for Fields {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#(#names => Ok(Self::#variants),)*
_ => Err(()),
}
}
}
impl ::std::fmt::Display for Fields {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
f.pad(self.to_str())
}
}
}
}
/// Creates a static with a borrowed field's default value.
fn create_default_static(field: &Field) -> TokenStream {
let Field { const_ident, default, ty, .. } = field;
let init = match default {
Some(default) => quote! { || #default },
None => quote! { ::std::default::Default::default },
};
quote! {
static #const_ident: ::std::sync::LazyLock<#ty> =
::std::sync::LazyLock::new(#init);
quote! { #vis #ident: #foundations::Settable<Self, #i> }
}
}
@ -448,19 +310,23 @@ fn create_inherent_impl(element: &Elem) -> TokenStream {
let new_func = create_new_func(element);
let with_field_methods = element.accessor_fields().map(create_with_field_method);
let push_field_methods = element.accessor_fields().map(create_push_field_method);
let field_methods = element.accessor_fields().map(create_field_method);
let field_in_methods = element.style_fields().map(create_field_in_method);
let set_field_methods = element.style_fields().map(create_set_field_method);
let style_consts = element.real_fields().map(|field| {
let Field { i, vis, ident, .. } = field;
quote! {
#vis const #ident: #foundations::Field<Self, #i>
= #foundations::Field::new();
}
});
quote! {
impl #ident {
#new_func
#(#with_field_methods)*
#(#push_field_methods)*
#(#field_methods)*
#(#field_in_methods)*
#(#set_field_methods)*
}
#[allow(non_upper_case_globals)]
impl #ident {
#(#style_consts)*
}
}
}
@ -476,8 +342,10 @@ fn create_new_func(element: &Elem) -> TokenStream {
let ident = &field.ident;
if field.required {
quote! { #ident }
} else {
} else if field.synthesized {
quote! { #ident: None }
} else {
quote! { #ident: #foundations::Settable::new() }
}
});
@ -491,254 +359,190 @@ fn create_new_func(element: &Elem) -> TokenStream {
/// Create a builder-style setter method for a field.
fn create_with_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, with_ident, push_ident, name, ty, .. } = field;
let Field { vis, ident, with_ident, name, ty, .. } = field;
let doc = format!("Builder-style setter for the [`{name}`](Self::{ident}) field.");
let expr = if field.required {
quote! { self.#ident = #ident }
} else if field.synthesized {
quote! { self.#ident = Some(#ident); }
} else {
quote! { self.#ident.set(#ident); }
};
quote! {
#[doc = #doc]
#vis fn #with_ident(mut self, #ident: #ty) -> Self {
self.#push_ident(#ident);
#expr;
self
}
}
}
/// Create a setter method for a field.
fn create_push_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, push_ident, name, ty, .. } = field;
let doc = format!("Setter for the [`{name}`](Self::{ident}) field.");
let expr = if field.required {
quote! { #ident }
} else {
quote! { Some(#ident) }
};
quote! {
#[doc = #doc]
#vis fn #push_ident(&mut self, #ident: #ty) {
self.#ident = #expr;
}
}
}
/// Create an accessor method for a field.
fn create_field_method(field: &Field) -> TokenStream {
let Field { vis, docs, ident, output, .. } = field;
if field.required {
quote! {
#[doc = #docs]
#vis fn #ident(&self) -> &#output {
&self.#ident
}
}
} else if field.synthesized {
quote! {
#[doc = #docs]
#[track_caller]
#vis fn #ident(&self) -> ::std::option::Option<&#output> {
self.#ident.as_ref()
}
}
} else {
let sig = if field.borrowed {
quote! { <'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output }
} else {
quote! { (&self, styles: #foundations::StyleChain) -> #output }
};
let mut value = create_style_chain_access(
field,
field.borrowed,
quote! { self.#ident.as_ref() },
);
if field.resolve {
value = quote! { #foundations::Resolve::resolve(#value, styles) };
}
quote! {
#[doc = #docs]
#vis fn #ident #sig {
#value
}
}
}
}
/// Create a style accessor method for a field.
fn create_field_in_method(field: &Field) -> TokenStream {
let Field { vis, ident_in, name, output, .. } = field;
let doc = format!("Access the `{name}` field in the given style chain.");
let ref_ = field.borrowed.then(|| quote! { & });
let mut value = create_style_chain_access(field, field.borrowed, quote! { None });
if field.resolve {
value = quote! { #foundations::Resolve::resolve(#value, styles) };
}
quote! {
#[doc = #doc]
#vis fn #ident_in(styles: #foundations::StyleChain) -> #ref_ #output {
#value
}
}
}
/// Create a style setter method for a field.
fn create_set_field_method(field: &Field) -> TokenStream {
let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field;
let doc = format!("Create a style property for the `{name}` field.");
quote! {
#[doc = #doc]
#vis fn #set_ident(#ident: #ty) -> #foundations::Property {
#foundations::Property::new::<Self, _>(
Fields::#enum_ident as u8,
#ident,
)
}
}
}
/// Create a style chain access method for a field.
fn create_style_chain_access(
field: &Field,
borrowed: bool,
inherent: TokenStream,
) -> TokenStream {
let Field { ty, default, enum_ident, const_ident, .. } = field;
let getter = match (field.fold, borrowed) {
(false, false) => quote! { get },
(false, true) => quote! { get_ref },
(true, _) => quote! { get_folded },
};
let default = if borrowed {
quote! { || &#const_ident }
} else {
match default {
Some(default) => quote! { || #default },
None => quote! { ::std::default::Default::default },
}
};
quote! {
styles.#getter::<#ty>(
<Self as #foundations::NativeElement>::elem(),
Fields::#enum_ident as u8,
#inherent,
#default,
)
}
}
/// Creates the element's `NativeElement` implementation.
fn create_native_elem_impl(element: &Elem) -> TokenStream {
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
let local_name = if element.can("LocalName") {
quote! { Some(<#foundations::Packed<#ident> as ::typst_library::text::LocalName>::local_name) }
} else {
quote! { None }
};
let fields = element.fields.iter().filter(|field| !field.internal).map(|field| {
let method = if field.external {
quote! { external }
} else if field.variadic {
quote! { variadic }
} else if field.required {
quote! { required }
} else if field.synthesized {
quote! { synthesized }
} else if field.ghost {
quote! { ghost }
} else {
quote! { settable }
};
let i = field.i;
quote! { #foundations::TypedFieldData::<Self>::#method::<#i>() }
});
let scope = if *scope {
quote! { <#ident as #foundations::NativeScope>::scope() }
} else {
quote! { #foundations::Scope::new() }
};
let params = element.doc_fields().map(create_param_info);
let data = quote! {
#foundations::NativeElementData {
name: #name,
title: #title,
docs: #docs,
keywords: &[#(#keywords),*],
construct: <#ident as #foundations::Construct>::construct,
set: <#ident as #foundations::Set>::set,
vtable: <#ident as #foundations::Capable>::vtable,
field_id: |name| name.parse().ok().map(|id: Fields| id as u8),
field_name: |id| id.try_into().ok().map(Fields::to_str),
field_from_styles: <#ident as #foundations::Fields>::field_from_styles,
local_name: #local_name,
scope: ::std::sync::LazyLock::new(|| #scope),
params: ::std::sync::LazyLock::new(|| ::std::vec![#(#params),*])
let field_arms = element
.fields
.iter()
.filter(|field| !field.internal && !field.external)
.map(|field| {
let Field { name, i, .. } = field;
quote! { #name => Some(#i) }
});
let field_id = quote! {
|name| match name {
#(#field_arms,)*
_ => None,
}
};
let with_keywords =
(!keywords.is_empty()).then(|| quote! { .with_keywords(&[#(#keywords),*]) });
let with_repr = element.can("Repr").then(|| quote! { .with_repr() });
let with_partial_eq = element.can("PartialEq").then(|| quote! { .with_partial_eq() });
let with_local_name = element.can("LocalName").then(|| quote! { .with_local_name() });
let with_scope = scope.then(|| quote! { .with_scope() });
quote! {
impl #foundations::NativeElement for #ident {
const ELEMENT: #foundations::TypedElementData<Self> =
#foundations::TypedElementData::new(
#name,
#title,
#docs,
&[#(#fields),*],
#field_id,
) #with_keywords
#with_repr
#with_partial_eq
#with_local_name
#with_scope;
fn data() -> &'static #foundations::NativeElementData {
static DATA: #foundations::NativeElementData = #data;
static DATA: #foundations::NativeElementData =
#foundations::NativeElementData::new::<#ident>();
&DATA
}
}
}
}
/// Creates a parameter info for a field.
fn create_param_info(field: &Field) -> TokenStream {
let Field {
name,
docs,
positional,
variadic,
required,
default,
ty,
..
} = field;
/// Creates the appropriate trait implementation for a field.
fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
let elem_ident = &element.ident;
let Field { i, ty, ident, default, positional, name, docs, .. } = field;
let named = !positional;
let settable = !field.required;
let default = match default {
Some(default) => quote! { || #default },
None => quote! { std::default::Default::default },
};
let default = if settable {
let default = default
.clone()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() });
let getters = quote! {
|elem| &elem.#ident,
|elem| &mut elem.#ident
};
if field.external {
quote! {
Some(|| <#ty as #foundations::IntoValue>::into_value(#default))
impl #foundations::ExternalField<#i> for #elem_ident {
type Type = #ty;
const FIELD: #foundations::ExternalFieldData<Self, #i> =
#foundations::ExternalFieldData::<Self, #i>::new(
#name,
#docs,
#default,
);
}
}
} else if field.required {
quote! {
impl #foundations::RequiredField<#i> for #elem_ident {
type Type = #ty;
const FIELD: #foundations::RequiredFieldData<Self, #i> =
#foundations::RequiredFieldData::<Self, #i>::new(
#name,
#docs,
#getters,
);
}
}
} else if field.synthesized {
quote! {
impl #foundations::SynthesizedField<#i> for #elem_ident {
type Type = #ty;
const FIELD: #foundations::SynthesizedFieldData<Self, #i> =
#foundations::SynthesizedFieldData::<Self, #i>::new(
#name,
#docs,
#getters,
);
}
}
} else {
quote! { None }
};
let slot = quote! {
|| {
static LOCK: ::std::sync::OnceLock<#ty> = ::std::sync::OnceLock::new();
&LOCK
}
};
let ty = if *variadic {
quote! { <#ty as #foundations::Container>::Inner }
} else {
quote! { #ty }
};
let with_fold = field.fold.then(|| quote! { .with_fold() });
let refable = (!field.fold).then(|| {
quote! {
impl #foundations::RefableProperty<#i> for #elem_ident {}
}
});
quote! {
#foundations::ParamInfo {
name: #name,
docs: #docs,
input: <#ty as #foundations::Reflect>::input(),
default: #default,
positional: #positional,
named: #named,
variadic: #variadic,
required: #required,
settable: #settable,
}
}
}
/// Creates the element's `PartialEq` implementation.
fn create_partial_eq_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;
let empty = element.eq_fields().next().is_none().then(|| quote! { true });
let fields = element.eq_fields().map(|field| &field.ident);
quote! {
impl PartialEq for #ident {
fn eq(&self, other: &Self) -> bool {
#empty
#(self.#fields == other.#fields)&&*
if field.ghost {
quote! {
impl #foundations::SettableProperty<#i> for #elem_ident {
type Type = #ty;
const PROPERTY: #foundations::SettablePropertyData<Self, #i> =
#foundations::SettablePropertyData::<Self, #i>::new(
#name,
#docs,
#positional,
#default,
#slot,
) #with_fold;
}
#refable
}
} else {
quote! {
impl #foundations::SettableField<#i> for #elem_ident {
type Type = #ty;
const FIELD: #foundations::SettableFieldData<Self, #i> =
#foundations::SettableFieldData::<Self, #i>::new(
#name,
#docs,
#positional,
#getters,
#default,
#slot,
) #with_fold;
}
#refable
}
}
}
@ -758,10 +562,12 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
let fields = element.struct_fields().map(|field| {
let ident = &field.ident;
if field.synthesized {
if field.required {
quote! { #ident }
} else if field.synthesized {
quote! { #ident: None }
} else {
quote! { #ident }
quote! { #ident: #foundations::Settable::from(#ident) }
}
});
@ -782,12 +588,12 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
fn create_set_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;
let handlers = element.set_fields().map(|field| {
let set_ident = &field.set_ident;
let field_ident = &field.ident;
let (prefix, value) = create_field_parser(field);
quote! {
#prefix
if let Some(value) = #value {
styles.set(Self::#set_ident(value));
styles.set(Self::#field_ident, value);
}
}
});
@ -861,196 +667,6 @@ fn create_capable_impl(element: &Elem) -> TokenStream {
}
}
/// Creates the element's `Fields` implementation.
fn create_fields_impl(element: &Elem) -> TokenStream {
let into_value = quote! { #foundations::IntoValue::into_value };
let visible_non_ghost = || element.visible_fields().filter(|field| !field.ghost);
// Fields that can be checked using the `has` method.
let has_arms = visible_non_ghost().map(|field| {
let Field { enum_ident, ident, .. } = field;
let expr = if field.required {
quote! { true }
} else {
quote! { self.#ident.is_some() }
};
quote! { Fields::#enum_ident => #expr }
});
// Fields that can be accessed using the `field` method.
let field_arms = visible_non_ghost().map(|field| {
let Field { enum_ident, ident, .. } = field;
let expr = if field.required {
quote! { Ok(#into_value(self.#ident.clone())) }
} else {
quote! { self.#ident.clone().map(#into_value).ok_or(#foundations::FieldAccessError::Unset) }
};
quote! { Fields::#enum_ident => #expr }
});
// Fields that can be accessed using the `field_with_styles` method.
let field_with_styles_arms = element.visible_fields().map(|field| {
let Field { enum_ident, ident, .. } = field;
let expr = if field.required {
quote! { Ok(#into_value(self.#ident.clone())) }
} else if field.synthesized {
quote! { self.#ident.clone().map(#into_value).ok_or(#foundations::FieldAccessError::Unset) }
} else {
let value = create_style_chain_access(
field,
false,
if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) },
);
quote! { Ok(#into_value(#value)) }
};
quote! { Fields::#enum_ident => #expr }
});
// Fields that can be accessed using the `field_from_styles` method.
let field_from_styles_arms = element.visible_fields().map(|field| {
let Field { enum_ident, .. } = field;
let expr = if field.required || field.synthesized {
quote! { Err(#foundations::FieldAccessError::Unknown) }
} else {
let value = create_style_chain_access(field, false, quote!(None));
quote! { Ok(#into_value(#value)) }
};
quote! { Fields::#enum_ident => #expr }
});
// Sets fields from the style chain.
let materializes = visible_non_ghost()
.filter(|field| !field.required && !field.synthesized)
.map(|field| {
let Field { ident, .. } = field;
let value = create_style_chain_access(
field,
false,
if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) },
);
if field.fold {
quote! { self.#ident = Some(#value); }
} else {
quote! {
if self.#ident.is_none() {
self.#ident = Some(#value);
}
}
}
});
// Creation of the `fields` dictionary for inherent fields.
let field_inserts = visible_non_ghost().map(|field| {
let Field { ident, name, .. } = field;
let string = quote! { #name.into() };
if field.required {
quote! {
fields.insert(#string, #into_value(self.#ident.clone()));
}
} else {
quote! {
if let Some(value) = &self.#ident {
fields.insert(#string, #into_value(value.clone()));
}
}
}
});
let Elem { ident, .. } = element;
let result = quote! {
Result<#foundations::Value, #foundations::FieldAccessError>
};
quote! {
impl #foundations::Fields for #ident {
type Enum = Fields;
fn has(&self, id: u8) -> bool {
let Ok(id) = Fields::try_from(id) else {
return false;
};
match id {
#(#has_arms,)*
_ => false,
}
}
fn field(&self, id: u8) -> #result {
let id = Fields::try_from(id)?;
match id {
#(#field_arms,)*
// This arm might be reached if someone tries to access an
// internal field.
_ => Err(#foundations::FieldAccessError::Unknown),
}
}
fn field_with_styles(&self, id: u8, styles: #foundations::StyleChain) -> #result {
let id = Fields::try_from(id)?;
match id {
#(#field_with_styles_arms,)*
// This arm might be reached if someone tries to access an
// internal field.
_ => Err(#foundations::FieldAccessError::Unknown),
}
}
fn field_from_styles(id: u8, styles: #foundations::StyleChain) -> #result {
let id = Fields::try_from(id)?;
match id {
#(#field_from_styles_arms,)*
// This arm might be reached if someone tries to access an
// internal field.
_ => Err(#foundations::FieldAccessError::Unknown),
}
}
fn materialize(&mut self, styles: #foundations::StyleChain) {
#(#materializes)*
}
fn fields(&self) -> #foundations::Dict {
let mut fields = #foundations::Dict::new();
#(#field_inserts)*
fields
}
}
}
}
/// Creates the element's `Repr` implementation.
fn create_repr_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;
let repr_format = format!("{}{{}}", element.name);
quote! {
impl #foundations::Repr for #ident {
fn repr(&self) -> ::ecow::EcoString {
let fields = #foundations::Fields::fields(self)
.into_iter()
.map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr()))
.collect::<Vec<_>>();
::ecow::eco_format!(
#repr_format,
#foundations::repr::pretty_array_like(&fields, false),
)
}
}
}
}
/// Creates the element's `Locatable` implementation.
fn create_locatable_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;
@ -1062,15 +678,3 @@ fn create_mathy_impl(element: &Elem) -> TokenStream {
let ident = &element.ident;
quote! { impl ::typst_library::math::Mathy for #foundations::Packed<#ident> {} }
}
/// Creates the element's `IntoValue` implementation.
fn create_into_value_impl(element: &Elem) -> TokenStream {
let Elem { ident, .. } = element;
quote! {
impl #foundations::IntoValue for #ident {
fn into_value(self) -> #foundations::Value {
#foundations::Value::Content(#foundations::Content::new(self))
}
}
}
}