Skip to content

Commit 276dd04

Browse files
bevy_reflect: Function reflection (#13152)
# Objective We're able to reflect types sooooooo... why not functions? The goal of this PR is to make functions callable within a dynamic context, where type information is not readily available at compile time. For example, if we have a function: ```rust fn add(left: i32, right: i32) -> i32 { left + right } ``` And two `Reflect` values we've already validated are `i32` types: ```rust let left: Box<dyn Reflect> = Box::new(2_i32); let right: Box<dyn Reflect> = Box::new(2_i32); ``` We should be able to call `add` with these values: ```rust // ????? let result: Box<dyn Reflect> = add.call_dynamic(left, right); ``` And ideally this wouldn't just work for functions, but methods and closures too! Right now, users have two options: 1. Manually parse the reflected data and call the function themselves 2. Rely on registered type data to handle the conversions for them For a small function like `add`, this isn't too bad. But what about for more complex functions? What about for many functions? At worst, this process is error-prone. At best, it's simply tedious. And this is assuming we know the function at compile time. What if we want to accept a function dynamically and call it with our own arguments? It would be much nicer if `bevy_reflect` could alleviate some of the problems here. ## Solution Added function reflection! This adds a `DynamicFunction` type to wrap a function dynamically. This can be called with an `ArgList`, which is a dynamic list of `Reflect`-containing `Arg` arguments. It returns a `FunctionResult` which indicates whether or not the function call succeeded, returning a `Reflect`-containing `Return` type if it did succeed. Many functions can be converted into this `DynamicFunction` type thanks to the `IntoFunction` trait. Taking our previous `add` example, this might look something like (explicit types added for readability): ```rust fn add(left: i32, right: i32) -> i32 { left + right } let mut function: DynamicFunction = add.into_function(); let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32); let result: Return = function.call(args).unwrap(); let value: Box<dyn Reflect> = result.unwrap_owned(); assert_eq!(value.take::<i32>().unwrap(), 4); ``` And it also works on closures: ```rust let add = |left: i32, right: i32| left + right; let mut function: DynamicFunction = add.into_function(); let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32); let result: Return = function.call(args).unwrap(); let value: Box<dyn Reflect> = result.unwrap_owned(); assert_eq!(value.take::<i32>().unwrap(), 4); ``` As well as methods: ```rust #[derive(Reflect)] struct Foo(i32); impl Foo { fn add(&mut self, value: i32) { self.0 += value; } } let mut foo = Foo(2); let mut function: DynamicFunction = Foo::add.into_function(); let args: ArgList = ArgList::new().push_mut(&mut foo).push_owned(2_i32); function.call(args).unwrap(); assert_eq!(foo.0, 4); ``` ### Limitations While this does cover many functions, it is far from a perfect system and has quite a few limitations. Here are a few of the limitations when using `IntoFunction`: 1. The lifetime of the return value is only tied to the lifetime of the first argument (useful for methods). This means you can't have a function like `(a: i32, b: &i32) -> &i32` without creating the `DynamicFunction` manually. 2. Only 15 arguments are currently supported. If the first argument is a (mutable) reference, this number increases to 16. 3. Manual implementations of `Reflect` will need to implement the new `FromArg`, `GetOwnership`, and `IntoReturn` traits in order to be used as arguments/return types. And some limitations of `DynamicFunction` itself: 1. All arguments share the same lifetime, or rather, they will shrink to the shortest lifetime. 2. Closures that capture their environment may need to have their `DynamicFunction` dropped before accessing those variables again (there is a `DynamicFunction::call_once` to make this a bit easier) 3. All arguments and return types must implement `Reflect`. While not a big surprise coming from `bevy_reflect`, this implementation could actually still work by swapping `Reflect` out with `Any`. Of course, that makes working with the arguments and return values a bit harder. 4. Generic functions are not supported (unless they have been manually monomorphized) And general, reflection gotchas: 1. `&str` does not implement `Reflect`. Rather, `&'static str` implements `Reflect` (the same is true for `&Path` and similar types). This means that `&'static str` is considered an "owned" value for the sake of generating arguments. Additionally, arguments and return types containing `&str` will assume it's `&'static str`, which is almost never the desired behavior. In these cases, the only solution (I believe) is to use `&String` instead. ### Followup Work This PR is the first of two PRs I intend to work on. The second PR will aim to integrate this new function reflection system into the existing reflection traits and `TypeInfo`. The goal would be to register and call a reflected type's methods dynamically. I chose not to do that in this PR since the diff is already quite large. I also want the discussion for both PRs to be focused on their own implementation. Another followup I'd like to do is investigate allowing common container types as a return type, such as `Option<&[mut] T>` and `Result<&[mut] T, E>`. This would allow even more functions to opt into this system. I chose to not include it in this one, though, for the same reasoning as previously mentioned. ### Alternatives One alternative I had considered was adding a macro to convert any function into a reflection-based counterpart. The idea would be that a struct that wraps the function would be created and users could specify which arguments and return values should be `Reflect`. It could then be called via a new `Function` trait. I think that could still work, but it will be a fair bit more involved, requiring some slightly more complex parsing. And it of course is a bit more work for the user, since they need to create the type via macro invocation. It also makes registering these functions onto a type a bit more complicated (depending on how it's implemented). For now, I think this is a fairly simple, yet powerful solution that provides the least amount of friction for users. --- ## Showcase Bevy now adds support for storing and calling functions dynamically using reflection! ```rust // 1. Take a standard Rust function fn add(left: i32, right: i32) -> i32 { left + right } // 2. Convert it into a type-erased `DynamicFunction` using the `IntoFunction` trait let mut function: DynamicFunction = add.into_function(); // 3. Define your arguments from reflected values let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32); // 4. Call the function with your arguments let result: Return = function.call(args).unwrap(); // 5. Extract the return value let value: Box<dyn Reflect> = result.unwrap_owned(); assert_eq!(value.take::<i32>().unwrap(), 4); ``` ## Changelog #### TL;DR - Added support for function reflection - Added a new `Function Reflection` example: https://github.com/bevyengine/bevy/blob/ba727898f2adff817838fc4cdb49871bbce37356/examples/reflection/function_reflection.rs#L1-L157 #### Details Added the following items: - `ArgError` enum - `ArgId` enum - `ArgInfo` struct - `ArgList` struct - `Arg` enum - `DynamicFunction` struct - `FromArg` trait (derived with `derive(Reflect)`) - `FunctionError` enum - `FunctionInfo` struct - `FunctionResult` alias - `GetOwnership` trait (derived with `derive(Reflect)`) - `IntoFunction` trait (with blanket implementation) - `IntoReturn` trait (derived with `derive(Reflect)`) - `Ownership` enum - `ReturnInfo` struct - `Return` enum --------- Co-authored-by: Periwink <[email protected]>
1 parent a1545dd commit 276dd04

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2195
-19
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,17 @@ description = "How dynamic types are used with reflection"
21522152
category = "Reflection"
21532153
wasm = false
21542154

2155+
[[example]]
2156+
name = "function_reflection"
2157+
path = "examples/reflection/function_reflection.rs"
2158+
doc-scrape-examples = true
2159+
2160+
[package.metadata.example.function_reflection]
2161+
name = "Function Reflection"
2162+
description = "Demonstrates how functions can be called dynamically using reflection"
2163+
category = "Reflection"
2164+
wasm = false
2165+
21552166
[[example]]
21562167
name = "generic_reflection"
21572168
path = "examples/reflection/generic_reflection.rs"

crates/bevy_reflect/compile_fail/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ compile_fail_utils = { path = "../../../tools/compile_fail_utils" }
1616
[[test]]
1717
name = "derive"
1818
harness = false
19+
20+
[[test]]
21+
name = "func"
22+
harness = false
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
fn main() -> compile_fail_utils::ui_test::Result<()> {
2-
compile_fail_utils::test("tests/reflect_derive")
2+
// compile_fail_utils::test("tests/reflect_derive")
3+
Ok(())
34
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() -> compile_fail_utils::ui_test::Result<()> {
2+
compile_fail_utils::test("tests/into_function")
3+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![allow(unused)]
2+
3+
use bevy_reflect::func::IntoFunction;
4+
use bevy_reflect::Reflect;
5+
6+
fn pass(_: i32) {}
7+
8+
fn too_many_arguments(
9+
arg0: i32,
10+
arg1: i32,
11+
arg2: i32,
12+
arg3: i32,
13+
arg4: i32,
14+
arg5: i32,
15+
arg6: i32,
16+
arg7: i32,
17+
arg8: i32,
18+
arg9: i32,
19+
arg10: i32,
20+
arg11: i32,
21+
arg12: i32,
22+
arg13: i32,
23+
arg14: i32,
24+
arg15: i32,
25+
) {
26+
}
27+
28+
struct Foo;
29+
30+
fn argument_not_reflect(foo: Foo) {}
31+
32+
fn main() {
33+
let _ = pass.into_function();
34+
35+
let _ = too_many_arguments.into_function();
36+
//~^ ERROR: no method named `into_function` found
37+
38+
let _ = argument_not_reflect.into_function();
39+
//~^ ERROR: no method named `into_function` found
40+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![allow(unused)]
2+
3+
use bevy_reflect::func::IntoFunction;
4+
use bevy_reflect::Reflect;
5+
6+
fn pass() -> i32 {
7+
123
8+
}
9+
10+
struct Foo;
11+
12+
fn return_not_reflect() -> Foo {
13+
Foo
14+
}
15+
16+
fn return_with_lifetime_pass<'a>(a: &'a String) -> &'a String {
17+
a
18+
}
19+
20+
fn return_with_invalid_lifetime<'a, 'b>(a: &'a String, b: &'b String) -> &'b String {
21+
b
22+
}
23+
24+
fn main() {
25+
let _ = pass.into_function();
26+
27+
let _ = return_not_reflect.into_function();
28+
//~^ ERROR: no method named `into_function` found
29+
30+
let _ = return_with_lifetime_pass.into_function();
31+
32+
let _ = return_with_invalid_lifetime.into_function();
33+
//~^ ERROR: no method named `into_function` found
34+
}

crates/bevy_reflect/derive/src/impls/enums.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::derive_data::{EnumVariantFields, ReflectEnum, StructField};
22
use crate::enum_utility::{EnumVariantOutputData, TryApplyVariantBuilder, VariantBuilder};
3-
use crate::impls::{impl_type_path, impl_typed};
3+
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
44
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult};
55
use proc_macro2::{Ident, Span};
66
use quote::quote;
@@ -65,6 +65,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
6565

6666
let type_path_impl = impl_type_path(reflect_enum.meta());
6767

68+
let function_impls = impl_function_traits(reflect_enum.meta(), &where_clause_options);
69+
6870
let get_type_registration_impl = reflect_enum.get_type_registration(&where_clause_options);
6971

7072
let (impl_generics, ty_generics, where_clause) =
@@ -79,6 +81,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
7981

8082
#type_path_impl
8183

84+
#function_impls
85+
8286
impl #impl_generics #bevy_reflect_path::Enum for #enum_path #ty_generics #where_reflect_clause {
8387
fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
8488
match self {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::derive_data::ReflectMeta;
2+
use crate::utility::WhereClauseOptions;
3+
use bevy_macro_utils::fq_std::FQResult;
4+
use quote::quote;
5+
6+
pub(crate) fn impl_from_arg(
7+
meta: &ReflectMeta,
8+
where_clause_options: &WhereClauseOptions,
9+
) -> proc_macro2::TokenStream {
10+
let bevy_reflect = meta.bevy_reflect_path();
11+
let type_path = meta.type_path();
12+
13+
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
14+
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
15+
16+
quote! {
17+
impl #impl_generics #bevy_reflect::func::args::FromArg for #type_path #ty_generics #where_reflect_clause {
18+
type Item<'from_arg> = #type_path #ty_generics;
19+
fn from_arg<'from_arg>(
20+
arg: #bevy_reflect::func::args::Arg<'from_arg>,
21+
info: &#bevy_reflect::func::args::ArgInfo,
22+
) -> #FQResult<Self::Item<'from_arg>, #bevy_reflect::func::args::ArgError> {
23+
arg.take_owned(info)
24+
}
25+
}
26+
27+
impl #impl_generics #bevy_reflect::func::args::FromArg for &'static #type_path #ty_generics #where_reflect_clause {
28+
type Item<'from_arg> = &'from_arg #type_path #ty_generics;
29+
fn from_arg<'from_arg>(
30+
arg: #bevy_reflect::func::args::Arg<'from_arg>,
31+
info: &#bevy_reflect::func::args::ArgInfo,
32+
) -> #FQResult<Self::Item<'from_arg>, #bevy_reflect::func::args::ArgError> {
33+
arg.take_ref(info)
34+
}
35+
}
36+
37+
impl #impl_generics #bevy_reflect::func::args::FromArg for &'static mut #type_path #ty_generics #where_reflect_clause {
38+
type Item<'from_arg> = &'from_arg mut #type_path #ty_generics;
39+
fn from_arg<'from_arg>(
40+
arg: #bevy_reflect::func::args::Arg<'from_arg>,
41+
info: &#bevy_reflect::func::args::ArgInfo,
42+
) -> #FQResult<Self::Item<'from_arg>, #bevy_reflect::func::args::ArgError> {
43+
arg.take_mut(info)
44+
}
45+
}
46+
}
47+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use crate::derive_data::ReflectMeta;
2+
use crate::impls::func::from_arg::impl_from_arg;
3+
use crate::impls::func::get_ownership::impl_get_ownership;
4+
use crate::impls::func::into_return::impl_into_return;
5+
use crate::utility::WhereClauseOptions;
6+
use quote::quote;
7+
8+
pub(crate) fn impl_function_traits(
9+
meta: &ReflectMeta,
10+
where_clause_options: &WhereClauseOptions,
11+
) -> proc_macro2::TokenStream {
12+
let get_ownership = impl_get_ownership(meta, where_clause_options);
13+
let from_arg = impl_from_arg(meta, where_clause_options);
14+
let into_return = impl_into_return(meta, where_clause_options);
15+
16+
quote! {
17+
#get_ownership
18+
19+
#from_arg
20+
21+
#into_return
22+
}
23+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::derive_data::ReflectMeta;
2+
use crate::utility::WhereClauseOptions;
3+
use quote::quote;
4+
5+
pub(crate) fn impl_get_ownership(
6+
meta: &ReflectMeta,
7+
where_clause_options: &WhereClauseOptions,
8+
) -> proc_macro2::TokenStream {
9+
let bevy_reflect = meta.bevy_reflect_path();
10+
let type_path = meta.type_path();
11+
12+
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
13+
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
14+
15+
quote! {
16+
impl #impl_generics #bevy_reflect::func::args::GetOwnership for #type_path #ty_generics #where_reflect_clause {
17+
fn ownership() -> #bevy_reflect::func::args::Ownership {
18+
#bevy_reflect::func::args::Ownership::Owned
19+
}
20+
}
21+
22+
impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ #type_path #ty_generics #where_reflect_clause {
23+
fn ownership() -> #bevy_reflect::func::args::Ownership {
24+
#bevy_reflect::func::args::Ownership::Ref
25+
}
26+
}
27+
28+
impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ mut #type_path #ty_generics #where_reflect_clause {
29+
fn ownership() -> #bevy_reflect::func::args::Ownership {
30+
#bevy_reflect::func::args::Ownership::Mut
31+
}
32+
}
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::derive_data::ReflectMeta;
2+
use crate::utility::WhereClauseOptions;
3+
use quote::quote;
4+
5+
pub(crate) fn impl_into_return(
6+
meta: &ReflectMeta,
7+
where_clause_options: &WhereClauseOptions,
8+
) -> proc_macro2::TokenStream {
9+
let bevy_reflect = meta.bevy_reflect_path();
10+
let type_path = meta.type_path();
11+
12+
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
13+
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
14+
15+
quote! {
16+
impl #impl_generics #bevy_reflect::func::IntoReturn for #type_path #ty_generics #where_reflect_clause {
17+
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> {
18+
#bevy_reflect::func::Return::Owned(Box::new(self))
19+
}
20+
}
21+
22+
impl #impl_generics #bevy_reflect::func::IntoReturn for &'static #type_path #ty_generics #where_reflect_clause {
23+
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> {
24+
#bevy_reflect::func::Return::Ref(self)
25+
}
26+
}
27+
28+
impl #impl_generics #bevy_reflect::func::IntoReturn for &'static mut #type_path #ty_generics #where_reflect_clause {
29+
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> {
30+
#bevy_reflect::func::Return::Mut(self)
31+
}
32+
}
33+
}
34+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub(crate) use function_impls::impl_function_traits;
2+
3+
mod from_arg;
4+
mod function_impls;
5+
mod get_ownership;
6+
mod into_return;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
mod enums;
2+
mod func;
23
mod structs;
34
mod tuple_structs;
45
mod typed;
56
mod values;
67

78
pub(crate) use enums::impl_enum;
9+
pub(crate) use func::impl_function_traits;
810
pub(crate) use structs::impl_struct;
911
pub(crate) use tuple_structs::impl_tuple_struct;
10-
pub(crate) use typed::impl_type_path;
11-
pub(crate) use typed::impl_typed;
12+
pub(crate) use typed::{impl_type_path, impl_typed};
1213
pub(crate) use values::impl_value;

crates/bevy_reflect/derive/src/impls/structs.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::impls::{impl_type_path, impl_typed};
1+
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
22
use crate::utility::ident_or_index;
33
use crate::ReflectStruct;
44
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
@@ -54,6 +54,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
5454

5555
let type_path_impl = impl_type_path(reflect_struct.meta());
5656

57+
let function_impls = impl_function_traits(reflect_struct.meta(), &where_clause_options);
58+
5759
let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options);
5860

5961
let (impl_generics, ty_generics, where_clause) = reflect_struct
@@ -71,6 +73,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
7173

7274
#type_path_impl
7375

76+
#function_impls
77+
7478
impl #impl_generics #bevy_reflect_path::Struct for #struct_path #ty_generics #where_reflect_clause {
7579
fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
7680
match name {

crates/bevy_reflect/derive/src/impls/tuple_structs.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::impls::{impl_type_path, impl_typed};
1+
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
22
use crate::ReflectStruct;
33
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult};
44
use quote::{quote, ToTokens};
@@ -46,6 +46,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
4646

4747
let type_path_impl = impl_type_path(reflect_struct.meta());
4848

49+
let function_impls = impl_function_traits(reflect_struct.meta(), &where_clause_options);
50+
4951
let (impl_generics, ty_generics, where_clause) = reflect_struct
5052
.meta()
5153
.type_path()
@@ -61,6 +63,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
6163

6264
#type_path_impl
6365

66+
#function_impls
67+
6468
impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_path #ty_generics #where_reflect_clause {
6569
fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> {
6670
match index {

crates/bevy_reflect/derive/src/impls/values.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::impls::{impl_type_path, impl_typed};
1+
use crate::impls::{impl_function_traits, impl_type_path, impl_typed};
22
use crate::utility::WhereClauseOptions;
33
use crate::ReflectMeta;
44
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult};
@@ -33,6 +33,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
3333

3434
let type_path_impl = impl_type_path(meta);
3535

36+
let function_impls = impl_function_traits(meta, &where_clause_options);
37+
3638
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
3739
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
3840
let get_type_registration_impl = meta.get_type_registration(&where_clause_options);
@@ -44,6 +46,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
4446

4547
#typed_impl
4648

49+
#function_impls
50+
4751
impl #impl_generics #bevy_reflect_path::Reflect for #type_path #ty_generics #where_reflect_clause {
4852
#[inline]
4953
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {

crates/bevy_reflect/derive/src/utility.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,17 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
154154
&self,
155155
where_clause: Option<&WhereClause>,
156156
) -> proc_macro2::TokenStream {
157+
let type_path = self.meta.type_path();
158+
let (_, ty_generics, _) = self.meta.type_path().generics().split_for_impl();
159+
157160
let required_bounds = self.required_bounds();
161+
158162
// Maintain existing where clause, if any.
159163
let mut generic_where_clause = if let Some(where_clause) = where_clause {
160164
let predicates = where_clause.predicates.iter();
161-
quote! {where Self: #required_bounds, #(#predicates,)*}
165+
quote! {where #type_path #ty_generics: #required_bounds, #(#predicates,)*}
162166
} else {
163-
quote!(where Self: #required_bounds,)
167+
quote!(where #type_path #ty_generics: #required_bounds,)
164168
};
165169

166170
// Add additional reflection trait bounds

0 commit comments

Comments
 (0)