use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase}; 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 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 { 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, capabilities: Vec, fields: Vec, } impl Elem { /// Calls the closure to produce a token stream if the /// element has the given 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. fn cannot(&self, name: &str) -> bool { !self.can(name) } } impl Elem { /// All fields that are not just external. fn real_fields(&self) -> impl Iterator + Clone { self.fields.iter().filter(|field| !field.external) } /// Fields that are present in the generated struct. fn struct_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| !field.ghost) } /// Fields that are relevant for equality. /// /// Synthesized fields are excluded to ensure equality before and after /// synthesis. fn eq_fields(&self) -> impl Iterator + Clone { self.struct_fields().filter(|field| !field.synthesized) } /// Fields that show up in the documentation. fn doc_fields(&self) -> impl Iterator + 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 /// 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 + 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 + Clone { self.construct_fields().filter(|field| !field.required) } /// Fields that can be accessed from the style chain. fn style_fields(&self) -> impl Iterator + Clone { self.real_fields() .filter(|field| !field.required && !field.synthesized) } /// Fields that are visible to the user. fn visible_fields(&self) -> impl Iterator + Clone { self.real_fields().filter(|field| !field.internal) } } 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, borrowed: bool, ghost: bool, synthesized: bool, parse: Option, default: Option, } /// 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::>>()?; 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 { 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 mut field = Field { 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(), default: parse_attr::(&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.resolve || 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 }; } validate_attrs(&attrs)?; Ok(field) } /// Produce the element's definition. fn create(element: &Elem) -> Result { // The struct itself. let struct_ = create_struct(element); 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 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 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. Ok(quote! { #struct_ const _: () = { #fields_enum #(#default_statics)* #inherent_impl #native_element_impl #fields_impl #capable_impl #construct_impl #set_impl #partial_eq_impl #repr_impl #locatable_impl #into_value_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)] #vis struct #ident { #(#fields,)* } } } /// Create a field declaration for the struct. fn create_field(field: &Field) -> TokenStream { let Field { vis, ident, ty, .. } = field; if field.required { quote! { #vis #ident: #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 for Fields { type Error = (); fn try_from(value: u8) -> Result { #(const #consts: u8 = Fields::#variants as u8;)* match value { #(#consts => Ok(Self::#variants),)* _ => Err(()), } } } impl ::std::str::FromStr for Fields { type Err = (); fn from_str(s: &str) -> Result { 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: ::once_cell::sync::Lazy<#ty> = ::once_cell::sync::Lazy::new(#init); } } /// 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.struct_fields().map(create_with_field_method); let push_field_methods = element.struct_fields().map(create_push_field_method); let field_methods = element.struct_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); quote! { impl #ident { #new_func #(#with_field_methods)* #(#push_field_methods)* #(#field_methods)* #(#field_in_methods)* #(#set_field_methods)* } } } /// 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 { quote! { #ident: None } } }); 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, push_ident, name, ty, .. } = field; let doc = format!("Builder-style setter for the [`{name}`](Self::{ident}) field."); quote! { #[doc = #doc] #vis fn #with_ident(mut self, #ident: #ty) -> Self { self.#push_ident(#ident); 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::( 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>( ::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::text::LocalName>::local_name) } } else { quote! { None } }; 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: #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 } } } } /// Creates a parameter info for a field. fn create_param_info(field: &Field) -> TokenStream { let Field { name, docs, positional, variadic, required, default, ty, .. } = field; let named = !positional; let settable = !field.required; let default = if settable { let default = default .clone() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }); quote! { Some(|| <#ty as #foundations::IntoValue>::into_value(#default)) } } else { quote! { None } }; 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, } } } /// 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)&&* } } } } /// 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.synthesized { quote! { #ident: None } } else { quote! { #ident } } }); quote! { impl #foundations::Construct for #ident { fn construct( engine: &mut ::typst::engine::Engine, args: &mut #foundations::Args, ) -> ::typst::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 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) } } } } /// 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_impl(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::() { // Safety: The vtable function doesn't require initialized // data, so it's fine to use a dangling pointer. return Some(unsafe { ::typst::util::fat::vtable(dangling as *const dyn #capability) }); } } }); quote! { unsafe impl #foundations::Capable for #ident { fn vtable(capability: ::std::any::TypeId) -> ::std::option::Option<*const ()> { let dangling = ::std::ptr::NonNull::<#foundations::Packed<#ident>>::dangling().as_ptr(); #(#checks)* None } } } } /// 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! { Some(#into_value(self.#ident.clone())) } } else { quote! { self.#ident.clone().map(#into_value) } }; 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! { Some(#into_value(self.#ident.clone())) } } else if field.synthesized { quote! { self.#ident.clone().map(#into_value) } } else { let value = create_style_chain_access( field, false, if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) }, ); quote! { Some(#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! { None } } else { let value = create_style_chain_access(field, false, quote!(None)); quote! { Some(#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; 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) -> Option<#foundations::Value> { let id = Fields::try_from(id).ok()?; match id { #(#field_arms,)* _ => None, } } fn field_with_styles(&self, id: u8, styles: #foundations::StyleChain) -> Option<#foundations::Value> { let id = Fields::try_from(id).ok()?; match id { #(#field_with_styles_arms,)* _ => None, } } fn field_from_styles(id: u8, styles: #foundations::StyleChain) -> Option<#foundations::Value> { let id = Fields::try_from(id).ok()?; match id { #(#field_from_styles_arms,)* _ => None, } } 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::>(); ::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; quote! { impl ::typst::introspection::Locatable 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)) } } } }