use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, Ident, Result, Token}; use crate::util::{ determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr, parse_flag, parse_string, parse_string_array, quote_option, validate_attrs, BlockWithReturn, }; /// Expand the `#[elem]` macro. pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result { let element = parse(stream, &body)?; create(&element) } /// Details about an element. struct Elem { name: String, title: String, scope: bool, keywords: Vec, docs: String, vis: syn::Visibility, ident: Ident, enum_ident: Ident, capabilities: Vec, fields: Vec, } impl Elem { /// Calls the closure to produce a token stream if the /// element does not have the given capability. fn unless_capability( &self, name: &str, closure: impl FnOnce() -> TokenStream, ) -> Option { self.capabilities .iter() .all(|capability| capability != name) .then(closure) } /// Calls the closure to produce a token stream if the /// element has the given capability. fn if_capability( &self, name: &str, closure: impl FnOnce() -> TokenStream, ) -> Option { self.capabilities .iter() .any(|capability| capability == name) .then(closure) } /// All fields. /// /// This includes: /// - Fields that are not external and therefore present in the struct. /// - Fields that are ghost fields. fn real_fields(&self) -> impl Iterator + Clone { self.fields.iter().filter(|field| !field.external) } /// Fields that are present in the struct. /// /// This includes: /// - Fields that are not external and therefore present in the struct. /// - Fields that are not ghost fields. fn present_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| !field.ghost) } /// Fields that are inherent to the element. fn inherent_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| field.inherent()) } /// Fields that can be set with style rules. /// /// 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. /// /// This includes: /// - Fields that are not synthesized. /// - Fields that are not inherent and therefore present at all times. /// - Fields that are not internal. fn settable_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| { !field.synthesized && field.settable() && (!field.internal || field.parse.is_some()) }) } /// Fields that are visible to the user. /// /// This includes: /// - Fields that are not internal. fn visible_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| !field.internal) } /// Fields that are relevant for equality. /// /// This includes: /// - Fields that are not synthesized (guarantees equality before and after synthesis). fn eq_fields(&self) -> impl Iterator + Clone { self.present_fields().filter(|field| !field.synthesized) } /// Fields that are relevant for `Construct` impl. /// /// This includes: /// - Fields that are not synthesized. fn construct_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| !field.synthesized) } } struct Field { ident: Ident, ident_in: Ident, with_ident: Ident, push_ident: Ident, set_ident: Ident, enum_ident: Ident, const_ident: Ident, vis: syn::Visibility, ty: syn::Type, output: syn::Type, name: String, docs: String, positional: bool, required: bool, variadic: bool, resolve: bool, fold: bool, internal: bool, external: bool, synthesized: bool, borrowed: bool, ghost: bool, parse: Option, default: Option, } impl Field { /// Whether the field is present on every instance of the element. fn inherent(&self) -> bool { (self.required || self.variadic) && !self.ghost } /// Whether the field can be used with set rules. fn settable(&self) -> bool { !self.inherent() } } /// The `..` in `#[elem(..)]`. struct Meta { scope: bool, name: Option, title: Option, keywords: Vec, capabilities: Vec, } impl Parse for Meta { fn parse(input: ParseStream) -> Result { Ok(Self { scope: parse_flag::(input)?, name: parse_string::(input)?, title: parse_string::(input)?, keywords: parse_string_array::(input)?, capabilities: Punctuated::::parse_terminated(input)? .into_iter() .collect(), }) } } /// Parse details about the element from its struct definition. fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result { 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 fields = named.named.iter().map(parse_field).collect::>()?; Ok(Elem { name, title, scope: meta.scope, keywords: meta.keywords, docs, vis: body.vis.clone(), ident: body.ident.clone(), capabilities: meta.capabilities, fields, enum_ident: Ident::new(&format!("{}Fields", body.ident), body.ident.span()), }) } fn parse_field(field: &syn::Field) -> Result { let Some(ident) = field.ident.clone() else { bail!(field, "expected named field"); }; if ident == "label" { bail!(ident, "invalid field name"); } 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 mut field = Field { name: ident.to_string().to_kebab_case(), docs: documentation(&attrs), internal: has_attr(&mut attrs, "internal"), external: has_attr(&mut attrs, "external"), positional, required, variadic, borrowed: has_attr(&mut attrs, "borrowed"), synthesized: has_attr(&mut attrs, "synthesized"), fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), ghost: has_attr(&mut attrs, "ghost"), parse: parse_attr(&mut attrs, "parse")?.flatten(), default: parse_attr::(&mut attrs, "default")?.flatten(), vis: field.vis.clone(), ident: ident.clone(), ident_in: Ident::new(&format!("{ident}_in"), ident.span()), with_ident: Ident::new(&format!("with_{ident}"), ident.span()), push_ident: Ident::new(&format!("push_{ident}"), ident.span()), set_ident: Ident::new(&format!("set_{ident}"), ident.span()), 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()), ty: field.ty.clone(), output: field.ty.clone(), }; if field.required && (field.fold || field.resolve) { bail!(ident, "required fields cannot be folded or resolved"); } if field.required && !field.positional { bail!(ident, "only positional fields can be required"); } if field.resolve { let output = &field.output; field.output = parse_quote! { <#output as #foundations::Resolve>::Output }; } if field.fold { let output = &field.output; field.output = parse_quote! { <#output as #foundations::Fold>::Output }; } validate_attrs(&attrs)?; Ok(field) } /// Produce the element's definition. fn create(element: &Elem) -> Result { let Elem { vis, ident, docs, .. } = element; if element.fields.iter().any(|field| field.ghost) && element .capabilities .iter() .all(|capability| capability != "Construct") { bail!(ident, "cannot have ghost fields and have `Construct` auto generated"); } let all = element.real_fields(); let present = element.present_fields(); let settable = all.clone().filter(|field| !field.synthesized && field.settable()); let fields = present.clone().map(create_field); let fields_enum = create_fields_enum(element); let new = create_new_func(element); let field_methods = all.clone().map(|field| create_field_method(element, field)); let field_in_methods = settable.clone().map(|field| create_field_in_method(element, field)); let with_field_methods = present.clone().map(create_with_field_method); let push_field_methods = present.clone().map(create_push_field_method); let field_style_methods = settable.clone().map(|field| create_set_field_method(element, field)); // Trait implementations. let native_element_impl = create_native_elem_impl(element); let construct_impl = element.unless_capability("Construct", || create_construct_impl(element)); let set_impl = element.unless_capability("Set", || create_set_impl(element)); let locatable_impl = element.if_capability("Locatable", || create_locatable_impl(element)); let partial_eq_impl = element.unless_capability("PartialEq", || create_partial_eq_impl(element)); let repr_impl = element.unless_capability("Repr", || create_repr_impl(element)); let label_and_location = element.unless_capability("Unlabellable", || { quote! { location: Option<::typst::introspection::Location>, label: Option<#foundations::Label>, prepared: bool, } }); Ok(quote! { #[doc = #docs] #[derive(Debug, Clone, Hash)] #[allow(clippy::derived_hash_with_manual_eq)] #vis struct #ident { span: ::typst::syntax::Span, #label_and_location guards: ::std::vec::Vec<#foundations::Guard>, #(#fields,)* } #fields_enum impl #ident { #new #(#field_methods)* #(#field_in_methods)* #(#with_field_methods)* #(#push_field_methods)* #(#field_style_methods)* } #native_element_impl #construct_impl #set_impl #locatable_impl #partial_eq_impl #repr_impl impl #foundations::IntoValue for #ident { fn into_value(self) -> #foundations::Value { #foundations::Value::Content(#foundations::Content::new(self)) } } }) } /// Create a field declaration. fn create_field(field: &Field) -> TokenStream { let Field { ident, ty, docs, required, synthesized, default, .. } = field; let ty = required.then(|| quote! { #ty }).unwrap_or_else(|| { if *synthesized && default.is_some() { quote! { #ty } } else { quote! { ::std::option::Option<#ty> } } }); quote! { #[doc = #docs] #ident: #ty } } /// Creates the element's enum for field identifiers. fn create_fields_enum(element: &Elem) -> TokenStream { let Elem { ident, enum_ident, .. } = element; let fields = element.real_fields().collect::>(); let field_names = fields.iter().map(|Field { name, .. }| name).collect::>(); let field_consts = fields .iter() .map(|Field { const_ident, .. }| const_ident) .collect::>(); let field_variants = fields .iter() .map(|Field { enum_ident, .. }| enum_ident) .collect::>(); let definitions = fields.iter().map(|Field { enum_ident, .. }| { quote! { #enum_ident } }); quote! { // To hide the private type const _: () = { impl #foundations::ElementFields for #ident { type Fields = #enum_ident; } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(u8)] pub enum #enum_ident { #(#definitions,)* Label = 255, } impl #enum_ident { /// Converts this field identifier to the field name. pub fn to_str(self) -> &'static str { match self { #(Self::#field_variants => #field_names,)* Self::Label => "label", } } } impl ::std::convert::TryFrom for #enum_ident { type Error = (); fn try_from(value: u8) -> Result { #(const #field_consts: u8 = #enum_ident::#field_variants as u8;)* match value { #(#field_consts => Ok(Self::#field_variants),)* 255 => Ok(Self::Label), _ => Err(()), } } } impl ::std::fmt::Display for #enum_ident { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { f.pad(self.to_str()) } } impl ::std::str::FromStr for #enum_ident { type Err = (); fn from_str(s: &str) -> Result { match s { #(#field_names => Ok(Self::#field_variants),)* "label" => Ok(Self::Label), _ => Err(()), } } } }; } } /// Create the `new` function for the element. fn create_new_func(element: &Elem) -> TokenStream { let relevant = element .fields .iter() .filter(|field| !field.external && !field.synthesized && field.inherent()); let params = relevant.clone().map(|Field { ident, ty, .. }| { quote! { #ident: #ty } }); let required = relevant.map(|Field { ident, .. }| { quote! { #ident } }); let defaults = element .fields .iter() .filter(|field| { !field.external && !field.inherent() && !field.synthesized && !field.ghost }) .map(|Field { ident, .. }| quote! { #ident: None }); let default_synthesized = element .fields .iter() .filter(|field| !field.external && field.synthesized && !field.ghost) .map(|Field { ident, default, .. }| { if let Some(expr) = default { quote! { #ident: #expr } } else { quote! { #ident: None } } }); let label_and_location = element.unless_capability("Unlabellable", || { quote! { location: None, label: None, prepared: false, } }); quote! { /// Create a new element. pub fn new(#(#params),*) -> Self { Self { span: ::typst::syntax::Span::detached(), #label_and_location guards: ::std::vec::Vec::with_capacity(0), #(#required,)* #(#defaults,)* #(#default_synthesized,)* } } } } /// Create a builder pattern method for a field. fn create_with_field_method(field: &Field) -> TokenStream { let Field { vis, ident, with_ident, name, ty, synthesized, default, .. } = field; let doc = format!("Set the [`{name}`](Self::{ident}) field."); let set = if field.inherent() || (*synthesized && default.is_some()) { quote! { self.#ident = #ident; } } else { quote! { self.#ident = Some(#ident); } }; quote! { #[doc = #doc] #vis fn #with_ident(mut self, #ident: #ty) -> Self { #set self } } } /// Create a set-style method for a field. fn create_push_field_method(field: &Field) -> TokenStream { let Field { vis, ident, push_ident, name, ty, synthesized, default, .. } = field; let doc = format!("Push the [`{name}`](Self::{ident}) field."); let set = if (field.inherent() && !synthesized) || (*synthesized && default.is_some()) { quote! { self.#ident = #ident; } } else { quote! { self.#ident = Some(#ident); } }; quote! { #[doc = #doc] #vis fn #push_ident(&mut self, #ident: #ty) { #set } } } /// Create a setter method for a field. fn create_set_field_method(element: &Elem, field: &Field) -> TokenStream { let elem = &element.ident; 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::Style { #foundations::Style::Property(#foundations::Property::new( ::elem(), <#elem as #foundations::ElementFields>::Fields::#enum_ident as u8, #ident, )) } } } /// Create a style chain access method for a field. fn create_field_in_method(element: &Elem, field: &Field) -> TokenStream { let Field { vis, ident_in, name, output, .. } = field; let doc = format!("Access the `{name}` field in the given style chain."); let access = create_style_chain_access(element, field, quote! { None }); let output = if field.borrowed { quote! { &#output } } else { quote! { #output } }; quote! { #[doc = #doc] #vis fn #ident_in(styles: #foundations::StyleChain) -> #output { #access } } } /// Create an accessor methods for a field. fn create_field_method(element: &Elem, field: &Field) -> TokenStream { let Field { vis, docs, ident, output, ghost, .. } = field; let inherent = if *ghost { quote! { None } } else { quote! { self.#ident.as_ref() } }; if (field.inherent() && !field.synthesized) || (field.synthesized && field.default.is_some()) { quote! { #[doc = #docs] #vis fn #ident(&self) -> &#output { &self.#ident } } } else if field.synthesized { quote! { #[doc = #docs] #[track_caller] #vis fn #ident(&self) -> &#output { self.#ident.as_ref().unwrap() } } } else if field.borrowed { let access = create_style_chain_access(element, field, inherent); quote! { #[doc = #docs] #vis fn #ident<'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output { #access } } } else { let access = create_style_chain_access(element, field, inherent); quote! { #[doc = #docs] #vis fn #ident(&self, styles: #foundations::StyleChain) -> #output { #access } } } } /// Create a style chain access method for a field. fn create_style_chain_access( element: &Elem, field: &Field, inherent: TokenStream, ) -> TokenStream { let elem = &element.ident; let Field { ty, default, enum_ident, .. } = field; let getter = match (field.fold, field.resolve, field.borrowed) { (false, false, false) => quote! { get }, (false, false, true) => quote! { get_borrowed }, (false, true, _) => quote! { get_resolve }, (true, false, _) => quote! { get_fold }, (true, true, _) => quote! { get_resolve_fold }, }; let default = default .clone() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); let (init, default) = field.fold.then(|| (None, quote! { || #default })).unwrap_or_else(|| ( Some(quote! { static DEFAULT: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(|| #default); }), quote! { &DEFAULT }, )); quote! { #init styles.#getter::<#ty>( ::elem(), <#elem as #foundations::ElementFields>::Fields::#enum_ident as u8, #inherent, #default, ) } } /// Creates the element's `Pack` implementation. fn create_native_elem_impl(element: &Elem) -> TokenStream { let Elem { name, ident, title, scope, keywords, docs, .. } = element; let vtable_func = create_vtable_func(element); let params = element .fields .iter() .filter(|field| !field.internal && !field.synthesized) .map(create_param_info); let scope = if *scope { quote! { <#ident as #foundations::NativeScope>::scope() } } else { quote! { #foundations::Scope::new() } }; // Fields that can be accessed using the `field` method. let field_matches = element.visible_fields().map(|field| { let elem = &element.ident; let name = &field.enum_ident; let field_ident = &field.ident; if field.ghost { quote! { <#elem as #foundations::ElementFields>::Fields::#name => None, } } else if field.inherent() { quote! { <#elem as #foundations::ElementFields>::Fields::#name => Some( #foundations::IntoValue::into_value(self.#field_ident.clone()) ), } } else { quote! { <#elem as #foundations::ElementFields>::Fields::#name => { self.#field_ident.clone().map(#foundations::IntoValue::into_value) } } } }); // Fields that can be set using the `set_field` method. let field_set_matches = element .visible_fields() .filter(|field| field.settable() && !field.synthesized && !field.ghost) .map(|field| { let elem = &element.ident; let name = &field.enum_ident; let field_ident = &field.ident; quote! { <#elem as #foundations::ElementFields>::Fields::#name => { self.#field_ident = Some(#foundations::FromValue::from_value(value)?); return Ok(()); } } }); // Fields that are inherent. let field_inherent_matches = element .visible_fields() .filter(|field| field.inherent() && !field.ghost) .map(|field| { let elem = &element.ident; let name = &field.enum_ident; let field_ident = &field.ident; quote! { <#elem as #foundations::ElementFields>::Fields::#name => { self.#field_ident = #foundations::FromValue::from_value(value)?; return Ok(()); } } }); // Fields that cannot be set or are internal create an error. let field_not_set_matches = element .real_fields() .filter(|field| field.internal || field.synthesized || field.ghost) .map(|field| { let elem = &element.ident; let ident = &field.enum_ident; let field_name = &field.name; if field.internal { // Internal fields create an error that they are unknown. let unknown_field = format!("unknown field `{field_name}` on `{name}`"); quote! { <#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#unknown_field), } } else { // Fields that cannot be set create an error that they are not settable. let not_settable = format!("cannot set `{field_name}` on `{name}`"); quote! { <#elem as #foundations::ElementFields>::Fields::#ident => ::typst::diag::bail!(#not_settable), } } }); // Statistically compute whether we need preparation or not. let needs_preparation = element .unless_capability("Unlabellable", || { element .capabilities .iter() .any(|capability| capability == "Locatable" || capability == "Synthesize") .then(|| quote! { !self.prepared }) .unwrap_or_else(|| quote! { self.label().is_some() && !self.prepared }) }) .unwrap_or_else(|| { assert!(element.capabilities.iter().all(|capability| capability != "Locatable" && capability != "Synthesize")); quote! { false } }); // Creation of the fields dictionary for inherent fields. let field_dict = element.inherent_fields().clone().map(|field| { let name = &field.name; let field_ident = &field.ident; let field_call = if name.len() > 15 { quote! { ::ecow::EcoString::from(#name).into() } } else { quote! { ::ecow::EcoString::inline(#name).into() } }; quote! { fields.insert( #field_call, #foundations::IntoValue::into_value(self.#field_ident.clone()) ); } }); // Creation of the fields dictionary for optional fields. let field_opt_dict = element .visible_fields() .filter(|field| !field.inherent() && !field.ghost) .clone() .map(|field| { let name = &field.name; let field_ident = &field.ident; let field_call = if name.len() > 15 { quote! { ::ecow::EcoString::from(#name).into() } } else { quote! { ::ecow::EcoString::inline(#name).into() } }; quote! { if let Some(value) = &self.#field_ident { fields.insert( #field_call, #foundations::IntoValue::into_value(value.clone()) ); } } }); let location = element .unless_capability("Unlabellable", || quote! { self.location }) .unwrap_or_else(|| quote! { None }); let set_location = element .unless_capability("Unlabellable", || { quote! { self.location = Some(location); } }) .unwrap_or_else(|| quote! { drop(location) }); let label = element .unless_capability("Unlabellable", || quote! { self.label }) .unwrap_or_else(|| quote! { None }); let set_label = element .unless_capability("Unlabellable", || { quote! { self.label = Some(label); } }) .unwrap_or_else(|| quote! { drop(label) }); let label_field = element .unless_capability("Unlabellable", || { quote! { self.label().map(#foundations::Value::Label) } }) .unwrap_or_else(|| quote! { None }); let mark_prepared = element .unless_capability("Unlabellable", || quote! { self.prepared = true; }) .unwrap_or_else(|| quote! {}); let prepared = element .unless_capability("Unlabellable", || quote! { self.prepared }) .unwrap_or_else(|| quote! { true }); let local_name = element .if_capability( "LocalName", || quote! { Some(<#ident as ::typst::text::LocalName>::local_name) }, ) .unwrap_or_else(|| quote! { None }); let unknown_field = format!("unknown field {{}} on {name}"); let label_error = format!("cannot set label on {name}"); 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: #vtable_func, field_id: |name| < <#ident as #foundations::ElementFields>::Fields as ::std::str::FromStr >::from_str(name).ok().map(|id| id as u8), field_name: |id| < <#ident as #foundations::ElementFields>::Fields as ::std::convert::TryFrom >::try_from(id).ok().map(<#ident as #foundations::ElementFields>::Fields::to_str), local_name: #local_name, scope: #foundations::Lazy::new(|| #scope), params: #foundations::Lazy::new(|| ::std::vec![#(#params),*]) } }; quote! { impl #foundations::NativeElement for #ident { fn data() -> &'static #foundations::NativeElementData { static DATA: #foundations::NativeElementData = #data; &DATA } fn dyn_elem(&self) -> #foundations::Element { #foundations::Element::of::() } fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) { ::hash(self, &mut hasher); } fn dyn_eq(&self, other: &#foundations::Content) -> bool { if let Some(other) = other.to::() { ::eq(self, other) } else { false } } fn dyn_clone(&self) -> ::std::sync::Arc { ::std::sync::Arc::new(Clone::clone(self)) } fn as_any(&self) -> &dyn ::std::any::Any { self } fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } fn into_any(self: ::std::sync::Arc) -> ::std::sync::Arc { self } fn span(&self) -> ::typst::syntax::Span { self.span } fn set_span(&mut self, span: ::typst::syntax::Span) { if self.span().is_detached() { self.span = span; } } fn label(&self) -> Option<#foundations::Label> { #label } fn set_label(&mut self, label: #foundations::Label) { #set_label } fn location(&self) -> Option<::typst::introspection::Location> { #location } fn set_location(&mut self, location: ::typst::introspection::Location) { #set_location } fn push_guard(&mut self, guard: #foundations::Guard) { self.guards.push(guard); } fn is_guarded(&self, guard: #foundations::Guard) -> bool { self.guards.contains(&guard) } fn is_pristine(&self) -> bool { self.guards.is_empty() } fn mark_prepared(&mut self) { #mark_prepared } fn needs_preparation(&self) -> bool { #needs_preparation } fn is_prepared(&self) -> bool { #prepared } fn field(&self, id: u8) -> Option<#foundations::Value> { let id = <#ident as #foundations::ElementFields>::Fields::try_from(id).ok()?; match id { <#ident as #foundations::ElementFields>::Fields::Label => #label_field, #(#field_matches)* _ => None, } } fn fields(&self) -> #foundations::Dict { let mut fields = #foundations::Dict::new(); #(#field_dict)* #(#field_opt_dict)* fields } fn set_field(&mut self, id: u8, value: #foundations::Value) -> ::typst::diag::StrResult<()> { let id = <#ident as #foundations::ElementFields>::Fields::try_from(id) .map_err(|_| ::ecow::eco_format!(#unknown_field, id))?; match id { #(#field_set_matches)* #(#field_inherent_matches)* #(#field_not_set_matches)* <#ident as #foundations::ElementFields>::Fields::Label => { ::typst::diag::bail!(#label_error); } } } } } } /// Creates the element's `Construct` implementation. fn create_construct_impl(element: &Elem) -> TokenStream { let ident = &element.ident; let pre = element.construct_fields().map(|field| { let (prefix, value) = create_field_parser(field); let ident = &field.ident; quote! { #prefix let #ident = #value; } }); let handlers = element .construct_fields() .filter(|field| field.settable()) .map(|field| { let push_ident = &field.push_ident; let ident = &field.ident; quote! { if let Some(value) = #ident { element.#push_ident(value); } } }); let defaults = element .construct_fields() .filter(|field| !field.settable()) .map(|field| &field.ident); quote! { impl #foundations::Construct for #ident { fn construct( engine: &mut ::typst::engine::Engine, args: &mut #foundations::Args, ) -> ::typst::diag::SourceResult<#foundations::Content> { #(#pre)* let mut element = Self::new(#(#defaults),*); #(#handlers)* Ok(#foundations::Content::new(element)) } } } } /// Creates the element's `Set` implementation. fn create_set_impl(element: &Elem) -> TokenStream { let ident = &element.ident; let handlers = element.settable_fields().map(|field| { let set_ident = &field.set_ident; let (prefix, value) = create_field_parser(field); quote! { #prefix if let Some(value) = #value { styles.set(Self::#set_ident(value)); } } }); quote! { impl #foundations::Set for #ident { fn set( engine: &mut ::typst::engine::Engine, args: &mut #foundations::Args, ) -> ::typst::diag::SourceResult<#foundations::Styles> { let mut styles = #foundations::Styles::new(); #(#handlers)* Ok(styles) } } } } /// Creates the element's `Locatable` implementation. fn create_locatable_impl(element: &Elem) -> TokenStream { let ident = &element.ident; quote! { impl ::typst::introspection::Locatable for #ident {} } } /// Creates the element's `PartialEq` implementation. fn create_partial_eq_impl(element: &Elem) -> TokenStream { let ident = &element.ident; let all = element.eq_fields().map(|field| &field.ident).collect::>(); let empty = all.is_empty().then(|| quote! { true }); quote! { impl PartialEq for #ident { fn eq(&self, other: &Self) -> bool { #empty #(self.#all == other.#all)&&* } } } } /// 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::NativeElement::fields(self).into_iter() .map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr())) .collect::>(); ::ecow::eco_format!(#repr_format, #foundations::repr::pretty_array_like(&fields, false)) } } } } /// Creates the element's casting vtable. fn create_vtable_func(element: &Elem) -> TokenStream { // Forbidden capabilities (i.e capabilities that are not object safe). const FORBIDDEN: &[&str] = &["Construct", "PartialEq", "Hash", "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 id == ::std::any::TypeId::of::() { let vtable = unsafe { let dangling = ::std::ptr::NonNull::<#ident>::dangling().as_ptr() as *const dyn #capability; ::typst::util::fat::vtable(dangling) }; return Some(vtable); } } }); quote! { |id| { #(#checks)* None } } } /// Creates a parameter info for a field. fn create_param_info(field: &Field) -> TokenStream { let Field { name, docs, positional, variadic, required, default, fold, ty, output, .. } = field; let named = !positional; let settable = field.settable(); let default_ty = if *fold { &output } else { &ty }; let default = quote_option(&settable.then(|| { let default = default .clone() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); quote! { || { let typed: #default_ty = #default; #foundations::IntoValue::into_value(typed) } } })); let ty = if *variadic { quote! { <#ty as #foundations::Container>::Inner } } else { quote! { #ty } }; 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, } } } /// 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) }