mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 06:38:03 +08:00
Move implicit attribute defaults into the attrs! key declarations to skip per-item boxing
This commit is contained in:
@@ -2,30 +2,42 @@ use crate::crate_ident::CrateIdent;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::ParseStream;
|
||||
use syn::{Attribute, Ident, Token, Type, braced, token};
|
||||
use syn::{Attribute, Expr, Ident, Token, Type, braced, token};
|
||||
|
||||
/// Implementation of the `attrs!` macro declaring typed attribute keys.
|
||||
///
|
||||
/// Grammar: `Name: Type`, comma-separated; `namespace { ... }` blocks nest and contribute a
|
||||
/// `namespace:` prefix to the key name, which is otherwise derived mechanically from the key
|
||||
/// ident (UpperCamel → snake_case).
|
||||
/// ident (UpperCamel → snake_case). An optional `= value` after the type declares the key's
|
||||
/// implicit default, overriding the value type's `Default` for items lacking the attribute.
|
||||
pub fn attrs_impl(input: TokenStream) -> syn::Result<TokenStream> {
|
||||
let entries: Entries = syn::parse2(input)?;
|
||||
let crate_ident = CrateIdent::default();
|
||||
let core = crate_ident.gcore()?;
|
||||
|
||||
let items = entries.0.iter().map(|entry| generate_entry(entry, core, "")).collect::<syn::Result<Vec<_>>>()?;
|
||||
let lookup = generate_implicit_default_lookup(&entries.0, core);
|
||||
|
||||
Ok(quote! {
|
||||
#(#items)*
|
||||
#lookup
|
||||
})
|
||||
}
|
||||
|
||||
struct Entries(Vec<Entry>);
|
||||
|
||||
enum Entry {
|
||||
Key { docs: Vec<Attribute>, ident: Ident, ty: Type },
|
||||
Namespace { docs: Vec<Attribute>, ident: Ident, entries: Vec<Entry> },
|
||||
Key {
|
||||
docs: Vec<Attribute>,
|
||||
ident: Ident,
|
||||
ty: Box<Type>,
|
||||
default: Option<Box<Expr>>,
|
||||
},
|
||||
Namespace {
|
||||
docs: Vec<Attribute>,
|
||||
ident: Ident,
|
||||
entries: Vec<Entry>,
|
||||
},
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for Entries {
|
||||
@@ -49,8 +61,14 @@ fn parse_entries(input: ParseStream) -> syn::Result<Vec<Entry>> {
|
||||
});
|
||||
} else {
|
||||
input.parse::<Token![:]>()?;
|
||||
let ty: Type = input.parse()?;
|
||||
entries.push(Entry::Key { docs, ident, ty });
|
||||
let ty = Box::new(input.parse::<Type>()?);
|
||||
let default = if input.peek(Token![=]) {
|
||||
input.parse::<Token![=]>()?;
|
||||
Some(Box::new(input.parse::<Expr>()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
entries.push(Entry::Key { docs, ident, ty, default });
|
||||
}
|
||||
if !input.is_empty() {
|
||||
input.parse::<Token![,]>()?;
|
||||
@@ -61,8 +79,15 @@ fn parse_entries(input: ParseStream) -> syn::Result<Vec<Entry>> {
|
||||
|
||||
fn generate_entry(entry: &Entry, core: &TokenStream, prefix: &str) -> syn::Result<TokenStream> {
|
||||
match entry {
|
||||
Entry::Key { docs, ident, ty } => {
|
||||
Entry::Key { docs, ident, ty, default } => {
|
||||
let name = key_name(ident, prefix);
|
||||
let implicit_default = default.as_ref().map(|value| {
|
||||
quote! {
|
||||
fn implicit_default() -> Self::Value {
|
||||
#value
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(quote! {
|
||||
#(#docs)*
|
||||
pub struct #ident;
|
||||
@@ -71,6 +96,7 @@ fn generate_entry(entry: &Entry, core: &TokenStream, prefix: &str) -> syn::Resul
|
||||
fn name() -> &'static str {
|
||||
#name
|
||||
}
|
||||
#implicit_default
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -88,6 +114,40 @@ fn generate_entry(entry: &Entry, core: &TokenStream, prefix: &str) -> syn::Resul
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a string-keyed lookup of the boxed implicit defaults for erased attribute code paths,
|
||||
/// or nothing if no key in this invocation declares a `= value` default.
|
||||
fn generate_implicit_default_lookup(entries: &[Entry], core: &TokenStream) -> Option<TokenStream> {
|
||||
let mut defaulted_keys = Vec::new();
|
||||
collect_defaulted_key_paths(entries, &TokenStream::new(), &mut defaulted_keys);
|
||||
|
||||
if defaulted_keys.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(quote! {
|
||||
/// The boxed implicit default for the key named `key`, if that key declares one with `= value` in `attrs!`.
|
||||
pub fn implicit_default_value(key: &str) -> ::std::option::Option<::std::boxed::Box<dyn #core::list::AnyAttributeValue>> {
|
||||
#(
|
||||
if key == <#defaulted_keys as #core::attr::Attr>::name() {
|
||||
return ::std::option::Option::Some(::std::boxed::Box::new(<#defaulted_keys as #core::attr::Attr>::implicit_default()));
|
||||
}
|
||||
)*
|
||||
::std::option::Option::None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Walks the entry tree collecting module-qualified paths (like `namespace::Key`) of keys that declare a default.
|
||||
fn collect_defaulted_key_paths(entries: &[Entry], module_path: &TokenStream, paths: &mut Vec<TokenStream>) {
|
||||
for entry in entries {
|
||||
match entry {
|
||||
Entry::Key { ident, default: Some(_), .. } => paths.push(quote!(#module_path #ident)),
|
||||
Entry::Key { .. } => {}
|
||||
Entry::Namespace { ident, entries, .. } => collect_defaulted_key_paths(entries, "e!(#module_path #ident::), paths),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_name(ident: &Ident, prefix: &str) -> String {
|
||||
let snake = snake_case(&ident.to_string());
|
||||
if prefix.is_empty() { snake } else { format!("{prefix}:{snake}") }
|
||||
|
||||
Reference in New Issue
Block a user