diff --git a/framework_crates/bones_schema/macros/src/lib.rs b/framework_crates/bones_schema/macros/src/lib.rs index 95dd82f7f8..efda2c13d5 100644 --- a/framework_crates/bones_schema/macros/src/lib.rs +++ b/framework_crates/bones_schema/macros/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; -use proc_macro2::{Punct, Spacing, TokenStream as TokenStream2, TokenTree as TokenTree2}; -use quote::{format_ident, quote, quote_spanned, spanned::Spanned}; -use venial::{GenericBound, StructFields}; +use proc_macro2::{Ident, Punct, Spacing, TokenStream as TokenStream2, TokenTree as TokenTree2}; +use quote::{format_ident, quote, quote_spanned, spanned::Spanned, TokenStreamExt}; +use venial::StructFields; /// Helper macro to bail out of the macro with a compile error. macro_rules! throw { @@ -282,19 +282,18 @@ pub fn derive_has_schema(input: TokenStream) -> TokenStream { let register_schema = if input.generic_params().is_some() { quote! { - static S: OnceLock>> = OnceLock::new(); let schema = { - S.get_or_init(Default::default) + #schema_mod::registry::GENERIC_SCHEMA_CACHE .read() - .get(&TypeId::of::()) + .get(&(TypeId::of::(), #variant_schema_name)) .copied() }; schema.unwrap_or_else(|| { let schema = compute_schema(); - S.get_or_init(Default::default) + #schema_mod::registry::GENERIC_SCHEMA_CACHE .write() - .insert(TypeId::of::(), schema); + .insert((TypeId::of::(), #variant_schema_name), schema); schema }) @@ -350,6 +349,14 @@ pub fn derive_has_schema(input: TokenStream) -> TokenStream { } })(); + if match &input { + venial::Declaration::Struct(s) => s.where_clause.is_some(), + venial::Declaration::Enum(e) => e.where_clause.is_some(), + _ => false, + } { + throw!(input, "Where clauses are not supported."); + } + let schema_register = quote! { #schema_mod::registry::SCHEMA_REGISTRY.register(#schema_mod::SchemaData { name: stringify!(#name).into(), @@ -366,38 +373,47 @@ pub fn derive_has_schema(input: TokenStream) -> TokenStream { }; if let Some(generic_params) = input.generic_params() { - let mut sync_send_generic_params = generic_params.clone(); - for (param, _) in sync_send_generic_params.params.iter_mut() { - let clone_bound = if !no_clone { quote!(+ Clone) } else { quote!() }; - param.bound = Some(GenericBound { - tk_colon: Punct::new(':', Spacing::Joint), - tokens: quote!(HasSchema #clone_bound ).into_iter().collect(), - }); + let mut impl_bounds = TokenStream2::new(); + for (param, comma) in generic_params.params.iter() { + let name = ¶m.name; + impl_bounds.extend(quote!(#name : HasSchema)); + if !no_clone { + impl_bounds.append(Punct::new('+', Spacing::Alone)); + impl_bounds.append(Ident::new("Clone", input.__span())); + } + if let Some(bound) = ¶m.bound { + impl_bounds.append(Punct::new('+', Spacing::Alone)); + impl_bounds.extend(bound.tokens.iter().cloned()); + } + impl_bounds.append(comma.clone()); } + + let struct_params = generic_params + .params + .items() + .map(|param| ¶m.name) + .map(|name| quote!(#name,)) + .collect::(); + quote! { - unsafe impl #sync_send_generic_params #schema_mod::HasSchema for #name #generic_params { + unsafe impl<#impl_bounds> #schema_mod::HasSchema for #name<#struct_params> { fn schema() -> &'static #schema_mod::Schema { - use ::std::sync::{OnceLock}; use ::std::any::TypeId; - use bones_utils::HashMap; - use parking_lot::RwLock; - static S: OnceLock>> = OnceLock::new(); let schema = { - S.get_or_init(Default::default) + #schema_mod::registry::GENERIC_SCHEMA_CACHE .read() - .get(&TypeId::of::()) + .get(&(TypeId::of::(), stringify!(#name))) .copied() }; schema.unwrap_or_else(|| { let schema = #schema_register; - S.get_or_init(Default::default) + #schema_mod::registry::GENERIC_SCHEMA_CACHE .write() - .insert(TypeId::of::(), schema); + .insert((TypeId::of::(), stringify!(name)), schema); schema }) - } } } diff --git a/framework_crates/bones_schema/src/lib.rs b/framework_crates/bones_schema/src/lib.rs index 5798335f35..4a8eef3a75 100644 --- a/framework_crates/bones_schema/src/lib.rs +++ b/framework_crates/bones_schema/src/lib.rs @@ -63,6 +63,11 @@ mod test { Set(T), } + #[derive(HasSchema, Clone, Default)] + #[schema_module(crate)] + #[repr(C)] + struct WrapperWithDefault(T); + #[derive(HasSchema, Clone, Copy, Debug, PartialEq, Eq, Default)] #[schema_module(crate)] #[repr(u8)] @@ -163,5 +168,22 @@ mod test { assert_eq!(A::schema().layout(), B::schema().layout()); assert_eq!(C::schema().layout(), D::schema().layout()); } + + #[test] + fn generic_no_clone() { + #[derive(HasSchema, Default)] + #[schema(no_clone)] + #[schema_module(crate)] + #[repr(C)] + struct Meta(T); + + #[derive(HasSchema, Default)] + #[schema(no_clone)] + #[schema_module(crate)] + #[repr(C)] + struct NotClonable; + + _ = Meta::::schema(); + } } } diff --git a/framework_crates/bones_schema/src/registry.rs b/framework_crates/bones_schema/src/registry.rs index 76b6dcd7e3..da589d79e7 100644 --- a/framework_crates/bones_schema/src/registry.rs +++ b/framework_crates/bones_schema/src/registry.rs @@ -2,11 +2,16 @@ use std::{ alloc::Layout, - sync::atomic::{AtomicU32, Ordering::SeqCst}, + any::TypeId, + sync::{ + atomic::{AtomicU32, Ordering::SeqCst}, + LazyLock, + }, }; use append_only_vec::AppendOnlyVec; -use bones_utils::Deref; +use bones_utils::{Deref, HashMap}; +use parking_lot::RwLock; use crate::prelude::*; @@ -143,6 +148,11 @@ pub static SCHEMA_REGISTRY: SchemaRegistry = SchemaRegistry { schemas: AppendOnlyVec::new(), }; +#[doc(hidden)] +pub static GENERIC_SCHEMA_CACHE: LazyLock< + RwLock>, +> = LazyLock::new(Default::default); + #[cfg(test)] mod test { use bones_utils::default;