Re-implement actions as derive macros instead of blanket impls

This commit is contained in:
Mikayla 2023-11-16 17:32:02 -08:00
parent a0e976599c
commit 4de2c0f7ef
No known key found for this signature in database
22 changed files with 360 additions and 288 deletions

View file

@ -15,48 +15,81 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::{parse_macro_input, DeriveInput, Error};
use crate::register_action::register_action;
pub fn action(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let attrs = input
.attrs
.into_iter()
.filter(|attr| !attr.path.is_ident("action"))
.collect::<Vec<_>>();
let attributes = quote! {
#[gpui::register_action]
#[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
#(#attrs)*
if input.generics.lt_token.is_some() {
return Error::new(name.span(), "Actions must be a concrete type")
.into_compile_error()
.into();
}
let is_unit_struct = match input.data {
syn::Data::Struct(struct_data) => struct_data.fields.is_empty(),
syn::Data::Enum(_) => false,
syn::Data::Union(_) => false,
};
let visibility = input.vis;
let output = match input.data {
syn::Data::Struct(ref struct_data) => match &struct_data.fields {
syn::Fields::Named(_) | syn::Fields::Unnamed(_) => {
let fields = &struct_data.fields;
quote! {
#attributes
#visibility struct #name #fields
}
let build_impl = if is_unit_struct {
quote! {
Ok(std::boxed::Box::new(Self {}))
}
} else {
quote! {
Ok(std::boxed::Box::new(gpui::serde_json::from_value::<Self>(value)?))
}
};
let register_action = register_action(&name);
let output = quote! {
const _: fn() = || {
fn assert_impl<T: ?Sized + for<'a> gpui::serde::Deserialize<'a> + ::std::cmp::PartialEq + ::std::clone::Clone>() {}
assert_impl::<#name>();
};
impl gpui::Action for #name {
fn name(&self) -> &'static str
{
::std::any::type_name::<#name>()
}
syn::Fields::Unit => {
quote! {
#attributes
#visibility struct #name;
}
fn debug_name() -> &'static str
where
Self: ::std::marker::Sized
{
::std::any::type_name::<#name>()
}
},
syn::Data::Enum(ref enum_data) => {
let variants = &enum_data.variants;
quote! {
#attributes
#visibility enum #name { #variants }
fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>>
where
Self: ::std::marker::Sized {
#build_impl
}
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
action
.as_any()
.downcast_ref::<Self>()
.map_or(false, |a| self == a)
}
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
::std::boxed::Box::new(self.clone())
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
}
_ => panic!("Expected a struct or an enum."),
#register_action
};
TokenStream::from(output)

View file

@ -11,14 +11,14 @@ pub fn style_helpers(args: TokenStream) -> TokenStream {
style_helpers::style_helpers(args)
}
#[proc_macro_attribute]
pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
action::action(attr, item)
#[proc_macro_derive(Action)]
pub fn action(input: TokenStream) -> TokenStream {
action::action(input)
}
#[proc_macro_attribute]
pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
register_action::register_action(attr, item)
register_action::register_action_macro(attr, item)
}
#[proc_macro_derive(Component, attributes(component))]

View file

@ -12,13 +12,54 @@
// gpui2::register_action_builder::<Foo>()
// }
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
use syn::{parse_macro_input, DeriveInput, Error};
pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn register_action_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let type_name = &input.ident;
let registration = register_action(&input.ident);
let has_action_derive = input
.attrs
.iter()
.find(|attr| {
(|| {
let meta = attr.parse_meta().ok()?;
meta.path().is_ident("derive").then(|| match meta {
syn::Meta::Path(_) => None,
syn::Meta::NameValue(_) => None,
syn::Meta::List(list) => list
.nested
.iter()
.find(|list| match list {
syn::NestedMeta::Meta(meta) => meta.path().is_ident("Action"),
syn::NestedMeta::Lit(_) => false,
})
.map(|_| true),
})?
})()
.unwrap_or(false)
})
.is_some();
if has_action_derive {
return Error::new(
input.ident.span(),
"The Action derive macro has already registered this action",
)
.into_compile_error()
.into();
}
TokenStream::from(quote! {
#input
#registration
})
}
pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
let static_slice_name =
format_ident!("__GPUI_ACTIONS_{}", type_name.to_string().to_uppercase());
@ -27,9 +68,7 @@ pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
type_name.to_string().to_lowercase()
);
let expanded = quote! {
#input
quote! {
#[doc(hidden)]
#[gpui::linkme::distributed_slice(gpui::__GPUI_ACTIONS)]
#[linkme(crate = gpui::linkme)]
@ -44,7 +83,5 @@ pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
build: <#type_name as gpui::Action>::build,
}
}
};
TokenStream::from(expanded)
}
}