bevy_reflect: Generic parameter info#15475
Merged
alice-i-cecile merged 13 commits intobevyengine:mainfrom Sep 30, 2024
Merged
Conversation
The `HashMap` not only takes up more memory, but is actually slightly slower in the common case.
Also switched to derived `Debug` impl
aecsocket
reviewed
Sep 27, 2024
Contributor
aecsocket
left a comment
There was a problem hiding this comment.
LGTM, just some questions
pablo-lua
reviewed
Sep 27, 2024
aecsocket
approved these changes
Sep 28, 2024
pablo-lua
reviewed
Sep 28, 2024
pablo-lua
approved these changes
Sep 28, 2024
Contributor
pablo-lua
left a comment
There was a problem hiding this comment.
Nice API for knowing Generics of a type, LGTM
alice-i-cecile
approved these changes
Sep 30, 2024
robtfm
pushed a commit
to robtfm/bevy
that referenced
this pull request
Oct 4, 2024
# Objective Currently, reflecting a generic type provides no information about the generic parameters. This means that you can't get access to the type of `T` in `Foo<T>` without creating custom type data (we do this for [`ReflectHandle`](https://docs.rs/bevy/0.14.2/bevy/asset/struct.ReflectHandle.html#method.asset_type_id)). ## Solution This PR makes it so that generic type parameters and generic const parameters are tracked in a `Generics` struct stored on the `TypeInfo` for a type. For example, `struct Foo<T, const N: usize>` will store `T` and `N` as a `TypeParamInfo` and `ConstParamInfo`, respectively. The stored information includes: - The name of the generic parameter (i.e. `T`, `N`, etc.) - The type of the generic parameter (remember that we're dealing with monomorphized types, so this will actually be a concrete type) - The default type/value, if any (e.g. `f32` in `T = f32` or `10` in `const N: usize = 10`) ### Caveats The only requirement for this to work is that the user does not opt-out of the automatic `TypePath` derive with `#[reflect(type_path = false)]`. Doing so prevents the macro code from 100% knowing that the generic type implements `TypePath`. This in turn means the generated `Typed` impl can't add generics to the type. There are two solutions for this—both of which I think we should explore in a future PR: 1. We could just not use `TypePath`. This would mean that we can't store the `Type` of the generic, but we can at least store the `TypeId`. 2. We could provide a way to opt out of the automatic `Typed` derive with a `#[reflect(typed = false)]` attribute. This would allow users to manually implement `Typed` to add whatever generic information they need (e.g. skipping a parameter that can't implement `TypePath` while the rest can). I originally thought about making `Generics` an enum with `Generic`, `NonGeneric`, and `Unavailable` variants to signify whether there are generics, no generics, or generics that cannot be added due to opting out of `TypePath`. I ultimately decided against this as I think it adds a bit too much complexity for such an uncommon problem. Additionally, user's don't necessarily _have_ to know the generics of a type, so just skipping them should generally be fine for now. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Showcase You can now access generic parameters via `TypeInfo`! ```rust #[derive(Reflect)] struct MyStruct<T, const N: usize>([T; N]); let generics = MyStruct::<f32, 10>::type_info().generics(); // Get by index: let t = generics.get(0).unwrap(); assert_eq!(t.name(), "T"); assert!(t.ty().is::<f32>()); assert!(!t.is_const()); // Or by name: let n = generics.get_named("N").unwrap(); assert_eq!(n.name(), "N"); assert!(n.ty().is::<usize>()); assert!(n.is_const()); ``` You can even access parameter defaults: ```rust #[derive(Reflect)] struct MyStruct<T = String, const N: usize = 10>([T; N]); let generics = MyStruct::<f32, 5>::type_info().generics(); let GenericInfo::Type(info) = generics.get_named("T").unwrap() else { panic!("expected a type parameter"); }; let default = info.default().unwrap(); assert!(default.is::<String>()); let GenericInfo::Const(info) = generics.get_named("N").unwrap() else { panic!("expected a const parameter"); }; let default = info.default().unwrap(); assert_eq!(default.downcast_ref::<usize>().unwrap(), &10); ```
Member
|
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1705 if you'd like to help out. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
Currently, reflecting a generic type provides no information about the generic parameters. This means that you can't get access to the type of
TinFoo<T>without creating custom type data (we do this forReflectHandle).Solution
This PR makes it so that generic type parameters and generic const parameters are tracked in a
Genericsstruct stored on theTypeInfofor a type.For example,
struct Foo<T, const N: usize>will storeTandNas aTypeParamInfoandConstParamInfo, respectively.The stored information includes:
T,N, etc.)f32inT = f32or10inconst N: usize = 10)Caveats
The only requirement for this to work is that the user does not opt-out of the automatic
TypePathderive with#[reflect(type_path = false)].Doing so prevents the macro code from 100% knowing that the generic type implements
TypePath. This in turn means the generatedTypedimpl can't add generics to the type.There are two solutions for this—both of which I think we should explore in a future PR:
TypePath. This would mean that we can't store theTypeof the generic, but we can at least store theTypeId.Typedderive with a#[reflect(typed = false)]attribute. This would allow users to manually implementTypedto add whatever generic information they need (e.g. skipping a parameter that can't implementTypePathwhile the rest can).I originally thought about making
Genericsan enum withGeneric,NonGeneric, andUnavailablevariants to signify whether there are generics, no generics, or generics that cannot be added due to opting out ofTypePath. I ultimately decided against this as I think it adds a bit too much complexity for such an uncommon problem.Additionally, users don't necessarily have to know the generics of a type, so just skipping them should generally be fine for now.
Testing
You can test locally by running:
Showcase
You can now access generic parameters via
TypeInfo!You can even access parameter defaults: