Skip to content

Commit

Permalink
implement TypeUuid for primitives and fix multiple-parameter generi…
Browse files Browse the repository at this point in the history
…cs having the same `TypeUuid` (#6633)

# Objective

- Fixes #5432 
- Fixes #6680

## Solution

- move code responsible for generating the `impl TypeUuid` from `type_uuid_derive` into a new function, `gen_impl_type_uuid`.
- this allows the new proc macro, `impl_type_uuid`, to call the code for generation.
- added struct `TypeUuidDef` and implemented `syn::Parse` to allow parsing of the input for the new macro.
- finally, used the new macro `impl_type_uuid` to implement `TypeUuid` for the standard library (in `crates/bevy_reflect/src/type_uuid_impl.rs`).
- fixes #6680 by doing a wrapping add of the param's index to its `TYPE_UUID`

Co-authored-by: dis-da-moe <[email protected]>
  • Loading branch information
dis-da-mor and dis-da-mor committed Feb 16, 2023
1 parent 8130729 commit 8853bef
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 98 deletions.
74 changes: 3 additions & 71 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,79 +11,11 @@ use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
ConstParam, DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta,
Result, Token, TypeParam,
parse::ParseStream, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
ConstParam, DeriveInput, Field, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token,
TypeParam,
};

struct AllTuples {
macro_ident: Ident,
start: usize,
end: usize,
idents: Vec<Ident>,
}

impl Parse for AllTuples {
fn parse(input: ParseStream) -> Result<Self> {
let macro_ident = input.parse::<Ident>()?;
input.parse::<Comma>()?;
let start = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let end = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let mut idents = vec![input.parse::<Ident>()?];
while input.parse::<Comma>().is_ok() {
idents.push(input.parse::<Ident>()?);
}

Ok(AllTuples {
macro_ident,
start,
end,
idents,
})
}
}

#[proc_macro]
pub fn all_tuples(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as AllTuples);
let len = input.end - input.start;
let mut ident_tuples = Vec::with_capacity(len);
for i in input.start..=input.end {
let idents = input
.idents
.iter()
.map(|ident| format_ident!("{}{}", ident, i));
if input.idents.len() < 2 {
ident_tuples.push(quote! {
#(#idents)*
});
} else {
ident_tuples.push(quote! {
(#(#idents),*)
});
}
}

let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = &ident_tuples[..i];
quote! {
#macro_ident!(#(#ident_tuples),*);
}
});
TokenStream::from(quote! {
#(
#invocations
)*
})
}

enum BundleFieldKind {
Component,
Ignore,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::{
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
TypeIdMap,
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::OwningPtr;
use bevy_utils::all_tuples;
use std::any::TypeId;

/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub mod prelude {
};
}

pub use bevy_ecs_macros::all_tuples;
pub use bevy_utils::all_tuples;

/// A specialized hashmap type with Key of `TypeId`
type TypeIdMap<V> = rustc_hash::FxHashMap<TypeId, V>;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use crate::{
storage::{ComponentSparseSet, Table, TableRow},
world::{Mut, Ref, World},
};
use bevy_ecs_macros::all_tuples;
pub use bevy_ecs_macros::WorldQuery;
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
use std::{cell::UnsafeCell, marker::PhantomData};

/// Types that can be fetched from a [`World`] using a [`Query`].
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/query/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::{
storage::{Column, ComponentSparseSet, Table, TableRow},
world::World,
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
use std::{cell::UnsafeCell, marker::PhantomData};

use super::ReadOnlyWorldQuery;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/schedule/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy_ecs_macros::all_tuples;
use bevy_utils::all_tuples;

use crate::{
schedule::{
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ecs/src/system/exclusive_function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::{
},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;

use bevy_utils::all_tuples;
use std::{any::TypeId, borrow::Cow, marker::PhantomData};

/// A function system that runs with exclusive [`World`] access.
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/system/exclusive_system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
system::{Local, SystemMeta, SystemParam, SystemState},
world::World,
};
use bevy_ecs_macros::all_tuples;
use bevy_utils::all_tuples;
use bevy_utils::synccell::SyncCell;

pub trait ExclusiveSystemParam: Sized {
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ecs/src/system/function_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use crate::{
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
world::{World, WorldId},
};
use bevy_ecs_macros::all_tuples;

use bevy_utils::all_tuples;
use std::{any::TypeId, borrow::Cow, marker::PhantomData};

use super::ReadOnlySystem;
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use crate::{
system::{Query, SystemMeta},
world::{FromWorld, World},
};
use bevy_ecs_macros::impl_param_set;
pub use bevy_ecs_macros::Resource;
pub use bevy_ecs_macros::SystemParam;
use bevy_ecs_macros::{all_tuples, impl_param_set};
use bevy_ptr::UnsafeCellDeref;
use bevy_utils::synccell::SyncCell;
use bevy_utils::{all_tuples, synccell::SyncCell};
use std::{
borrow::Cow,
fmt::Debug,
Expand Down
9 changes: 9 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ mod type_uuid;
mod utility;

use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct};
use crate::type_uuid::gen_impl_type_uuid;
use proc_macro::TokenStream;
use quote::quote;
use reflect_value::ReflectValueDef;
use syn::spanned::Spanned;
use syn::{parse_macro_input, DeriveInput};
use type_uuid::TypeUuidDef;

pub(crate) static REFLECT_ATTRIBUTE_NAME: &str = "reflect";
pub(crate) static REFLECT_VALUE_ATTRIBUTE_NAME: &str = "reflect_value";
Expand Down Expand Up @@ -185,3 +187,10 @@ pub fn impl_from_reflect_value(input: TokenStream) -> TokenStream {
def.traits.unwrap_or_default(),
))
}

/// Derives `TypeUuid` for the given type. This is used internally to implement `TypeUuid` on foreign types, such as those in the std. This macro should be used in the format of `<[Generic Params]> [Type (Path)], [Uuid (String Literal)]`.
#[proc_macro]
pub fn impl_type_uuid(input: TokenStream) -> TokenStream {
let def = parse_macro_input!(input as TypeUuidDef);
gen_impl_type_uuid(def)
}
71 changes: 56 additions & 15 deletions crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,17 @@ extern crate proc_macro;

use bevy_macro_utils::BevyManifest;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::*;
use uuid::Uuid;

/// Parses input from a derive of `TypeUuid`.
pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let mut ast: DeriveInput = syn::parse(input).unwrap();
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");

let ast: DeriveInput = syn::parse(input).unwrap();
// Build the trait implementation
let name = &ast.ident;

ast.generics.type_params_mut().for_each(|param| {
param
.bounds
.push(syn::parse_quote!(#bevy_reflect_path::TypeUuid));
});

let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let type_ident = ast.ident;

let mut uuid = None;
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
Expand Down Expand Up @@ -50,24 +42,73 @@ pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::To

let uuid =
uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found.");
gen_impl_type_uuid(TypeUuidDef {
type_ident,
generics: ast.generics,
uuid,
})
}

/// Generates an implementation of `TypeUuid`. If there any generics, the `TYPE_UUID` will be a composite of the generic types' `TYPE_UUID`.
pub(crate) fn gen_impl_type_uuid(def: TypeUuidDef) -> proc_macro::TokenStream {
let uuid = def.uuid;
let mut generics = def.generics;
let ty = def.type_ident;

let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");

generics.type_params_mut().for_each(|param| {
param
.bounds
.push(syn::parse_quote!(#bevy_reflect_path::TypeUuid));
});

let bytes = uuid
.as_bytes()
.iter()
.map(|byte| format!("{byte:#X}"))
.map(|byte_str| syn::parse_str::<LitInt>(&byte_str).unwrap());

let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

let base = quote! { #bevy_reflect_path::Uuid::from_bytes([#( #bytes ),*]) };
let type_uuid = ast.generics.type_params().fold(base, |acc, param| {
let type_uuid = generics.type_params().enumerate().fold(base, |acc, (index, param)| {
let ident = &param.ident;
let param_uuid = quote!(
#bevy_reflect_path::Uuid::from_u128(<#ident as #bevy_reflect_path::TypeUuid>::TYPE_UUID.as_u128().wrapping_add(#index as u128))
);
quote! {
#bevy_reflect_path::__macro_exports::generate_composite_uuid(#acc, <#ident as #bevy_reflect_path::TypeUuid>::TYPE_UUID)
#bevy_reflect_path::__macro_exports::generate_composite_uuid(#acc, #param_uuid)
}
});

let gen = quote! {
impl #impl_generics #bevy_reflect_path::TypeUuid for #name #type_generics #where_clause {
impl #impl_generics #bevy_reflect_path::TypeUuid for #ty #type_generics #where_clause {
const TYPE_UUID: #bevy_reflect_path::Uuid = #type_uuid;
}
};
gen.into()
}

/// A struct containing the data required to generate an implementation of `TypeUuid`. This can be generated by either [`impl_type_uuid!`][crate::impl_type_uuid!] or [`type_uuid_derive`].
pub(crate) struct TypeUuidDef {
pub type_ident: Ident,
pub generics: Generics,
pub uuid: Uuid,
}

impl Parse for TypeUuidDef {
fn parse(input: ParseStream) -> Result<Self> {
let type_ident = input.parse::<Ident>()?;
let generics = input.parse::<Generics>()?;
input.parse::<Token![,]>()?;
let uuid = input.parse::<LitStr>()?.value();
let uuid = Uuid::parse_str(&uuid).map_err(|err| input.error(format!("{}", err)))?;

Ok(Self {
type_ident,
generics,
uuid,
})
}
}
1 change: 1 addition & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod tuple_struct;
mod type_info;
mod type_registry;
mod type_uuid;
mod type_uuid_impl;
mod impls {
#[cfg(feature = "glam")]
mod glam;
Expand Down
32 changes: 32 additions & 0 deletions crates/bevy_reflect/src/type_uuid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,36 @@ mod test {

assert_eq!(uuid_a, uuid_b);
}

#[test]
fn test_multiple_generic_uuid() {
#[derive(TypeUuid)]
#[uuid = "35c8a7d3-d4b3-4bd7-b847-1118dc78092f"]
struct TestGeneric<A, B> {
_value_a: A,
_value_b: B,
}
assert_ne!(
TestGeneric::<f32, bool>::TYPE_UUID,
TestGeneric::<bool, f32>::TYPE_UUID
);
}

#[test]
fn test_primitive_generic_uuid() {
test_impl_type_uuid(&true);
test_impl_type_uuid(&Some(true));
test_impl_type_uuid(&TestDeriveStruct::<bool> { _value: true });

assert_ne!(Option::<bool>::TYPE_UUID, Option::<f32>::TYPE_UUID);

assert_ne!(<[bool; 0]>::TYPE_UUID, <[bool; 1]>::TYPE_UUID);
assert_ne!(<[bool; 0]>::TYPE_UUID, <[f32; 0]>::TYPE_UUID);

assert_ne!(
<(bool, bool)>::TYPE_UUID,
<(bool, bool, bool, bool)>::TYPE_UUID
);
assert_ne!(<(bool, f32)>::TYPE_UUID, <(f32, bool)>::TYPE_UUID);
}
}
Loading

0 comments on commit 8853bef

Please sign in to comment.