extern crate proc_macro;
use proc_macro::TokenStream;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
use syn::{Data, DataStruct, Fields};
#[proc_macro_derive(FieldEnum)]
pub fn field_enum_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let fields = if let Data::Struct(DataStruct {
fields: Fields::Named(ref fields_named),
..
}) = input.data
{
&fields_named.named
} else {
panic!("FieldEnum can only be used with named fields");
};
let unit_structs = fields.iter().map(|field| {
let ident = field.ident.as_ref().unwrap();
let struct_name = format_ident!("{}Field", ident.to_string().to_case(Case::Pascal));
let ty = &field.ty;
quote! {
pub struct #struct_name;
impl Field for #struct_name {
type FieldType = #ty;
fn select<'a>(&self, store: &'a #name) -> &'a Self::FieldType {
&store.#ident
}
fn set(&self, store: &mut #name, value: Self::FieldType) {
store.#ident = value;
}
}
}
});
let expanded = quote! {
pub trait Field {
type FieldType;
fn select<'a>(&self, store: &'a #name) -> &'a Self::FieldType;
fn set(&self, store: &mut #name, value: Self::FieldType);
}
#(#unit_structs)*
};
TokenStream::from(expanded)
}
#[proc_macro_derive(Crete)]
pub fn all_together_derive(input: TokenStream) -> TokenStream {
let input_clone = input.clone();
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let field_enum_tokens: TokenStream2 = field_enum_derive(input_clone).into();
let crete_store_ident = format_ident!("CRETE_{}", struct_name.to_string().to_uppercase());
let implements_clone = input.attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
let mut found_clone = false;
let _ = attr.parse_nested_meta(|nested| {
if nested.path.is_ident("Clone") {
found_clone = true;
}
Ok(())
});
found_clone
} else {
false
}
});
dbg!(&implements_clone);
let clone_fn = if implements_clone {
quote! {
pub fn clone() -> #struct_name
{
let mut a = #crete_store_ident.read().expect("RWLock poisoned").clone();
::std::sync::Arc::<#struct_name>::make_mut(&mut a).clone()
}
}
} else {
quote! {} };
let code = quote! {
#field_enum_tokens
static #crete_store_ident: ::std::sync::LazyLock<::std::sync::RwLock<::std::sync::Arc<#struct_name>>> =
::std::sync::LazyLock::new(|| ::std::sync::RwLock::new(::std::sync::Arc::new(#struct_name::new())));
impl #struct_name {
pub fn new() -> Self {
#struct_name::default()
}
pub fn read() -> ::std::sync::Arc<#struct_name> {
#crete_store_ident.read().expect("RWLock poisoned").clone()
}
#clone_fn
pub fn write(self) {
*#crete_store_ident.write().expect("RWLock poisoned") = ::std::sync::Arc::new(self);
}
pub fn select_ref<F: Field>(&self, field: F) -> &F::FieldType {
field.select(self)
}
pub fn get<F, R>(field: F, f: impl FnOnce(&F::FieldType) -> R) -> R
where
F: Field,
{
let store = #struct_name::read();
let field_ref = store.select_ref(field);
f(field_ref)
}
pub fn select<F: Field>(field: F) -> F::FieldType
where
F::FieldType: Clone,
{
#struct_name::read().select_ref(field).clone()
}
pub fn set<F>(field: F, value: F::FieldType)
where
F: Field,
{
let mut store_write_guard = #crete_store_ident.write().expect("RWLock poisoned");
let mut s = ::std::sync::Arc::get_mut(&mut *store_write_guard).expect("You have an extra strong reference. Did you have a read() binding in the same scope?");
field.set(&mut s, value);
*store_write_guard = ::std::sync::Arc::new(::std::mem::take(s));
}
pub fn update(f: impl FnOnce(&mut #struct_name) -> ()) {
let mut store_write_guard = #crete_store_ident.write().expect("RWLock poisoned");
let mut s = ::std::sync::Arc::get_mut(&mut *store_write_guard).expect("You have an extra strong reference. Did you have a read() binding in the same scope?");
f(&mut s);
*store_write_guard = ::std::sync::Arc::new(::std::mem::take(s));
}
pub async fn update_async<F>(f: F)
where
F: AsyncFnOnce(&mut #struct_name),
{
let mut store_write_guard = #crete_store_ident.write().expect("RWLock poisoned");
let mut s = ::std::sync::Arc::get_mut(&mut *store_write_guard).expect("You have an extra strong reference. Did you have a read() binding in the same scope?");
f(&mut s).await;
*store_write_guard = ::std::sync::Arc::new(::std::mem::take(s));
}
}
};
code.into()
}