mirror of
https://github.com/typst/typst
synced 2025-07-27 22:37:54 +08:00
676 lines
22 KiB
Rust
676 lines
22 KiB
Rust
use heck::ToKebabCase;
|
|
use proc_macro2::TokenStream;
|
|
use quote::{format_ident, quote};
|
|
use syn::parse::{Parse, ParseStream};
|
|
use syn::punctuated::Punctuated;
|
|
use syn::{Ident, Result, Token};
|
|
|
|
use crate::util::{
|
|
determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr,
|
|
parse_flag, parse_string, parse_string_array, validate_attrs, BlockWithReturn,
|
|
};
|
|
|
|
/// Expand the `#[elem]` macro.
|
|
pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
|
let element = parse(stream, &body)?;
|
|
create(&element)
|
|
}
|
|
|
|
/// Details about an element.
|
|
struct Elem {
|
|
/// The element's name as exposed to Typst.
|
|
name: String,
|
|
/// The element's title case name.
|
|
title: String,
|
|
/// Whether this element has an associated scope defined by the `#[scope]` macro.
|
|
scope: bool,
|
|
/// A list of alternate search terms for this element.
|
|
keywords: Vec<String>,
|
|
/// The documentation for this element as a string.
|
|
docs: String,
|
|
/// The element's visibility.
|
|
vis: syn::Visibility,
|
|
/// The struct name for this element given in Rust.
|
|
ident: Ident,
|
|
/// The list of capabilities for this element.
|
|
capabilities: Vec<Ident>,
|
|
/// The fields of this element.
|
|
fields: Vec<Field>,
|
|
}
|
|
|
|
impl Elem {
|
|
/// Whether the element has the given trait listed as a capability.
|
|
fn can(&self, name: &str) -> bool {
|
|
self.capabilities.iter().any(|capability| capability == name)
|
|
}
|
|
|
|
/// Whether the element does not have the given trait listed as a
|
|
/// capability.
|
|
fn cannot(&self, name: &str) -> bool {
|
|
!self.can(name)
|
|
}
|
|
}
|
|
|
|
impl Elem {
|
|
/// All fields that are not just external.
|
|
fn real_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.fields.iter().filter(|field| !field.external)
|
|
}
|
|
|
|
/// Fields that are present in the generated struct.
|
|
fn struct_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.real_fields().filter(|field| !field.ghost)
|
|
}
|
|
|
|
/// Fields that get accessor, with, and push methods.
|
|
fn accessor_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.struct_fields().filter(|field| !field.required)
|
|
}
|
|
|
|
/// Fields that are relevant for `Construct` impl.
|
|
///
|
|
/// The reason why fields that are `parse` and internal are allowed is
|
|
/// because it's a pattern used a lot for parsing data from the input and
|
|
/// then storing it in a field.
|
|
fn construct_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.real_fields().filter(|field| {
|
|
field.parse.is_some() || (!field.synthesized && !field.internal)
|
|
})
|
|
}
|
|
|
|
/// Fields that can be configured with set rules.
|
|
fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.construct_fields().filter(|field| !field.required)
|
|
}
|
|
}
|
|
|
|
/// 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 `with_{ident}`.
|
|
with_ident: Ident,
|
|
/// The visibility of this field.
|
|
vis: syn::Visibility,
|
|
/// The type of this field.
|
|
ty: syn::Type,
|
|
/// The field's identifier as exposed to Typst.
|
|
name: String,
|
|
/// The documentation for this field as a string.
|
|
docs: String,
|
|
/// Whether this field is positional (as opposed to named).
|
|
positional: bool,
|
|
/// Whether this field is required.
|
|
required: bool,
|
|
/// Whether this field is variadic; that is, has its values
|
|
/// taken from a variable number of arguments.
|
|
variadic: 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 `#[ghost]` attribute.
|
|
ghost: bool,
|
|
/// Whether this field has a `#[synthesized]` attribute.
|
|
synthesized: bool,
|
|
/// The contents of the `#[parse({..})]` attribute, if any.
|
|
parse: Option<BlockWithReturn>,
|
|
/// The contents of the `#[default(..)]` attribute, if any.
|
|
default: Option<syn::Expr>,
|
|
}
|
|
|
|
/// The `..` in `#[elem(..)]`.
|
|
struct Meta {
|
|
scope: bool,
|
|
name: Option<String>,
|
|
title: Option<String>,
|
|
keywords: Vec<String>,
|
|
capabilities: Vec<Ident>,
|
|
}
|
|
|
|
impl Parse for Meta {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
Ok(Self {
|
|
scope: parse_flag::<kw::scope>(input)?,
|
|
name: parse_string::<kw::name>(input)?,
|
|
title: parse_string::<kw::title>(input)?,
|
|
keywords: parse_string_array::<kw::keywords>(input)?,
|
|
capabilities: Punctuated::<Ident, Token![,]>::parse_terminated(input)?
|
|
.into_iter()
|
|
.collect(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Parse details about the element from its struct definition.
|
|
fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
|
|
let meta: Meta = syn::parse2(stream)?;
|
|
let (name, title) = determine_name_and_title(
|
|
meta.name,
|
|
meta.title,
|
|
&body.ident,
|
|
Some(|base| base.trim_end_matches("Elem")),
|
|
)?;
|
|
|
|
let docs = documentation(&body.attrs);
|
|
|
|
let syn::Fields::Named(named) = &body.fields else {
|
|
bail!(body, "expected named fields");
|
|
};
|
|
|
|
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")
|
|
{
|
|
bail!(
|
|
body.ident,
|
|
"cannot have public ghost fields and an auto-generated constructor"
|
|
);
|
|
}
|
|
|
|
Ok(Elem {
|
|
name,
|
|
title,
|
|
scope: meta.scope,
|
|
keywords: meta.keywords,
|
|
docs,
|
|
vis: body.vis.clone(),
|
|
ident: body.ident.clone(),
|
|
capabilities: meta.capabilities,
|
|
fields,
|
|
})
|
|
}
|
|
|
|
fn parse_field(field: &syn::Field) -> Result<Field> {
|
|
let Some(ident) = field.ident.clone() else {
|
|
bail!(field, "expected named field");
|
|
};
|
|
|
|
if ident == "label" {
|
|
bail!(ident, "invalid field name `label`");
|
|
}
|
|
|
|
let mut attrs = field.attrs.clone();
|
|
let variadic = has_attr(&mut attrs, "variadic");
|
|
let required = has_attr(&mut attrs, "required") || variadic;
|
|
let positional = has_attr(&mut attrs, "positional") || required;
|
|
|
|
let field = Field {
|
|
i: 0,
|
|
ident: ident.clone(),
|
|
with_ident: format_ident!("with_{ident}"),
|
|
vis: field.vis.clone(),
|
|
ty: field.ty.clone(),
|
|
name: ident.to_string().to_kebab_case(),
|
|
docs: documentation(&attrs),
|
|
positional,
|
|
required,
|
|
variadic,
|
|
fold: has_attr(&mut attrs, "fold"),
|
|
internal: has_attr(&mut attrs, "internal"),
|
|
external: has_attr(&mut attrs, "external"),
|
|
ghost: has_attr(&mut attrs, "ghost"),
|
|
synthesized: has_attr(&mut attrs, "synthesized"),
|
|
parse: parse_attr(&mut attrs, "parse")?.flatten(),
|
|
default: parse_attr::<syn::Expr>(&mut attrs, "default")?.flatten(),
|
|
};
|
|
|
|
if field.required && field.synthesized {
|
|
bail!(ident, "required fields cannot be synthesized");
|
|
}
|
|
|
|
if (field.required || field.synthesized)
|
|
&& (field.default.is_some() || field.fold || field.ghost)
|
|
{
|
|
bail!(ident, "required and synthesized fields cannot be default, fold, or ghost");
|
|
}
|
|
|
|
validate_attrs(&attrs)?;
|
|
|
|
Ok(field)
|
|
}
|
|
|
|
/// Produce the element's definition.
|
|
fn create(element: &Elem) -> Result<TokenStream> {
|
|
// The struct itself.
|
|
let struct_ = create_struct(element);
|
|
|
|
// Implementations.
|
|
let inherent_impl = create_inherent_impl(element);
|
|
let native_element_impl = create_native_elem_impl(element);
|
|
let field_impls =
|
|
element.fields.iter().map(|field| create_field_impl(element, field));
|
|
let construct_impl =
|
|
element.cannot("Construct").then(|| create_construct_impl(element));
|
|
let set_impl = element.cannot("Set").then(|| create_set_impl(element));
|
|
let locatable_impl = element.can("Locatable").then(|| create_locatable_impl(element));
|
|
let mathy_impl = element.can("Mathy").then(|| create_mathy_impl(element));
|
|
|
|
// We use a const block to create an anonymous scope, as to not leak any
|
|
// local definitions.
|
|
Ok(quote! {
|
|
#struct_
|
|
|
|
const _: () = {
|
|
#inherent_impl
|
|
#native_element_impl
|
|
#(#field_impls)*
|
|
#construct_impl
|
|
#set_impl
|
|
#locatable_impl
|
|
#mathy_impl
|
|
};
|
|
})
|
|
}
|
|
|
|
/// Create the struct definition itself.
|
|
fn create_struct(element: &Elem) -> TokenStream {
|
|
let Elem { vis, ident, docs, .. } = element;
|
|
|
|
let debug = element.cannot("Debug").then(|| quote! { Debug, });
|
|
let fields = element.struct_fields().map(create_field);
|
|
|
|
quote! {
|
|
#[doc = #docs]
|
|
#[derive(#debug Clone, Hash)]
|
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
|
#[allow(rustdoc::broken_intra_doc_links)]
|
|
#vis struct #ident {
|
|
#(#fields,)*
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a field declaration for the struct.
|
|
fn create_field(field: &Field) -> TokenStream {
|
|
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! { #vis #ident: #foundations::Settable<Self, #i> }
|
|
}
|
|
}
|
|
|
|
/// Create the inherent implementation of the struct.
|
|
fn create_inherent_impl(element: &Elem) -> TokenStream {
|
|
let Elem { ident, .. } = element;
|
|
|
|
let new_func = create_new_func(element);
|
|
let with_field_methods = element.accessor_fields().map(create_with_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)*
|
|
}
|
|
#[allow(non_upper_case_globals)]
|
|
impl #ident {
|
|
#(#style_consts)*
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create the `new` function for the element.
|
|
fn create_new_func(element: &Elem) -> TokenStream {
|
|
let params = element
|
|
.struct_fields()
|
|
.filter(|field| field.required)
|
|
.map(|Field { ident, ty, .. }| quote! { #ident: #ty });
|
|
|
|
let fields = element.struct_fields().map(|field| {
|
|
let ident = &field.ident;
|
|
if field.required {
|
|
quote! { #ident }
|
|
} else if field.synthesized {
|
|
quote! { #ident: None }
|
|
} else {
|
|
quote! { #ident: #foundations::Settable::new() }
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
/// Create a new instance of the element.
|
|
pub fn new(#(#params),*) -> Self {
|
|
Self { #(#fields,)* }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a builder-style setter method for a field.
|
|
fn create_with_field_method(field: &Field) -> TokenStream {
|
|
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 {
|
|
#expr;
|
|
self
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates the element's `NativeElement` implementation.
|
|
fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
|
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
|
|
|
|
let fields = element.fields.iter().filter(|field| !field.internal).map(|field| {
|
|
let i = field.i;
|
|
if field.external {
|
|
quote! { #foundations::ExternalFieldData::<#ident, #i>::vtable() }
|
|
} else if field.variadic {
|
|
quote! { #foundations::RequiredFieldData::<#ident, #i>::vtable_variadic() }
|
|
} else if field.required {
|
|
quote! { #foundations::RequiredFieldData::<#ident, #i>::vtable() }
|
|
} else if field.synthesized {
|
|
quote! { #foundations::SynthesizedFieldData::<#ident, #i>::vtable() }
|
|
} else if field.ghost {
|
|
quote! { #foundations::SettablePropertyData::<#ident, #i>::vtable() }
|
|
} else {
|
|
quote! { #foundations::SettableFieldData::<#ident, #i>::vtable() }
|
|
}
|
|
});
|
|
|
|
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 capable_func = create_capable_func(element);
|
|
|
|
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! {
|
|
unsafe impl #foundations::NativeElement for #ident {
|
|
const ELEM: #foundations::Element = #foundations::Element::from_vtable({
|
|
static STORE: #foundations::LazyElementStore
|
|
= #foundations::LazyElementStore::new();
|
|
static VTABLE: #foundations::ContentVtable =
|
|
#foundations::ContentVtable::new::<#ident>(
|
|
#name,
|
|
#title,
|
|
#docs,
|
|
&[#(#fields),*],
|
|
#field_id,
|
|
#capable_func,
|
|
|| &STORE,
|
|
) #with_keywords
|
|
#with_repr
|
|
#with_partial_eq
|
|
#with_local_name
|
|
#with_scope
|
|
.erase();
|
|
&VTABLE
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 default = match default {
|
|
Some(default) => quote! { || #default },
|
|
None => quote! { std::default::Default::default },
|
|
};
|
|
|
|
if field.external {
|
|
quote! {
|
|
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,
|
|
|elem| &elem.#ident,
|
|
);
|
|
}
|
|
}
|
|
} 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,
|
|
|elem| &elem.#ident,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
let slot = quote! {
|
|
|| {
|
|
static LOCK: ::std::sync::OnceLock<#ty> = ::std::sync::OnceLock::new();
|
|
&LOCK
|
|
}
|
|
};
|
|
|
|
let with_fold = field.fold.then(|| quote! { .with_fold() });
|
|
let refable = (!field.fold).then(|| {
|
|
quote! {
|
|
impl #foundations::RefableProperty<#i> for #elem_ident {}
|
|
}
|
|
});
|
|
|
|
if field.ghost {
|
|
quote! {
|
|
impl #foundations::SettableProperty<#i> for #elem_ident {
|
|
type Type = #ty;
|
|
const FIELD: #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,
|
|
|elem| &elem.#ident,
|
|
|elem| &mut elem.#ident,
|
|
#default,
|
|
#slot,
|
|
) #with_fold;
|
|
}
|
|
#refable
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates the element's `Construct` implementation.
|
|
fn create_construct_impl(element: &Elem) -> TokenStream {
|
|
let ident = &element.ident;
|
|
let setup = element.construct_fields().map(|field| {
|
|
let (prefix, value) = create_field_parser(field);
|
|
let ident = &field.ident;
|
|
quote! {
|
|
#prefix
|
|
let #ident = #value;
|
|
}
|
|
});
|
|
|
|
let fields = element.struct_fields().map(|field| {
|
|
let ident = &field.ident;
|
|
if field.required {
|
|
quote! { #ident }
|
|
} else if field.synthesized {
|
|
quote! { #ident: None }
|
|
} else {
|
|
quote! { #ident: #foundations::Settable::from(#ident) }
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
impl #foundations::Construct for #ident {
|
|
fn construct(
|
|
engine: &mut ::typst_library::engine::Engine,
|
|
args: &mut #foundations::Args,
|
|
) -> ::typst_library::diag::SourceResult<#foundations::Content> {
|
|
#(#setup)*
|
|
Ok(#foundations::Content::new(Self { #(#fields),* }))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates the element's `Set` implementation.
|
|
fn create_set_impl(element: &Elem) -> TokenStream {
|
|
let ident = &element.ident;
|
|
let handlers = element.set_fields().map(|field| {
|
|
let field_ident = &field.ident;
|
|
let (prefix, value) = create_field_parser(field);
|
|
quote! {
|
|
#prefix
|
|
if let Some(value) = #value {
|
|
styles.set(Self::#field_ident, value);
|
|
}
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
impl #foundations::Set for #ident {
|
|
fn set(
|
|
engine: &mut ::typst_library::engine::Engine,
|
|
args: &mut #foundations::Args,
|
|
) -> ::typst_library::diag::SourceResult<#foundations::Styles> {
|
|
let mut styles = #foundations::Styles::new();
|
|
#(#handlers)*
|
|
Ok(styles)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create argument parsing code for a field.
|
|
fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
|
|
if let Some(BlockWithReturn { prefix, expr }) = &field.parse {
|
|
return (quote! { #(#prefix);* }, quote! { #expr });
|
|
}
|
|
|
|
let name = &field.name;
|
|
let value = if field.variadic {
|
|
quote! { args.all()? }
|
|
} else if field.required {
|
|
quote! { args.expect(#name)? }
|
|
} else if field.positional {
|
|
quote! { args.find()? }
|
|
} else {
|
|
quote! { args.named(#name)? }
|
|
};
|
|
|
|
(quote! {}, value)
|
|
}
|
|
|
|
/// Creates the element's casting vtable.
|
|
fn create_capable_func(element: &Elem) -> TokenStream {
|
|
// Forbidden capabilities (i.e capabilities that are not object safe).
|
|
const FORBIDDEN: &[&str] =
|
|
&["Debug", "PartialEq", "Hash", "Construct", "Set", "Repr", "LocalName"];
|
|
|
|
let ident = &element.ident;
|
|
let relevant = element
|
|
.capabilities
|
|
.iter()
|
|
.filter(|&ident| !FORBIDDEN.contains(&(&ident.to_string() as &str)));
|
|
|
|
let checks = relevant.map(|capability| {
|
|
quote! {
|
|
if capability == ::std::any::TypeId::of::<dyn #capability>() {
|
|
// Safety: The vtable function doesn't require initialized
|
|
// data, so it's fine to use a dangling pointer.
|
|
return Some(unsafe {
|
|
::typst_utils::fat::vtable(dangling as *const dyn #capability)
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
|capability| {
|
|
let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr();
|
|
#(#checks)*
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates the element's `Locatable` implementation.
|
|
fn create_locatable_impl(element: &Elem) -> TokenStream {
|
|
let ident = &element.ident;
|
|
quote! { impl ::typst_library::introspection::Locatable for #foundations::Packed<#ident> {} }
|
|
}
|
|
|
|
/// Creates the element's `Mathy` implementation.
|
|
fn create_mathy_impl(element: &Elem) -> TokenStream {
|
|
let ident = &element.ident;
|
|
quote! { impl ::typst_library::math::Mathy for #foundations::Packed<#ident> {} }
|
|
}
|