From 59c0521690e86a3d6606494899bb3fd5ad1fe007 Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Sun, 22 Sep 2024 07:19:12 -0700 Subject: [PATCH] bevy_reflect: Add `Function` trait (#15205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective While #13152 added function reflection, it didn't really make functions reflectable. Instead, it made it so that they can be called with reflected arguments and return reflected data. But functions themselves cannot be reflected. In other words, we can't go from `DynamicFunction` to `dyn PartialReflect`. ## Solution Allow `DynamicFunction` to actually be reflected. This PR adds the `Function` reflection subtrait (and corresponding `ReflectRef`, `ReflectKind`, etc.). With this new trait, we're able to implement `PartialReflect` on `DynamicFunction`. ### Implementors `Function` is currently only implemented for `DynamicFunction<'static>`. This is because we can't implement it generically over all functions—even those that implement `IntoFunction`. What about `DynamicFunctionMut`? Well, this PR does **not** implement `Function` for `DynamicFunctionMut`. The reasons for this are a little complicated, but it boils down to mutability. `DynamicFunctionMut` requires `&mut self` to be invoked since it wraps a `FnMut`. However, we can't really model this well with `Function`. And if we make `DynamicFunctionMut` wrap its internal `FnMut` in a `Mutex` to allow for `&self` invocations, then we run into either concurrency issues or recursion issues (or, in the worst case, both). So for the time-being, we won't implement `Function` for `DynamicFunctionMut`. It will be better to evaluate it on its own. And we may even consider the possibility of removing it altogether if it adds too much complexity to the crate. ### Dynamic vs Concrete One of the issues with `DynamicFunction` is the fact that it's both a dynamic representation (like `DynamicStruct` or `DynamicList`) and the only way to represent a function. Because of this, it's in a weird middle ground where we can't easily implement full-on `Reflect`. That would require `Typed`, but what static `TypeInfo` could it provide? Just that it's a `DynamicFunction`? None of the other dynamic types implement `Typed`. However, by not implementing `Reflect`, we lose the ability to downcast back to our `DynamicStruct`. Our only option is to call `Function::clone_dynamic`, which clones the data rather than by simply downcasting. This works in favor of the `PartialReflect::try_apply` implementation since it would have to clone anyways, but is definitely not ideal. This is also the reason I had to add `Debug` as a supertrait on `Function`. For now, this PR chooses not to implement `Reflect` for `DynamicFunction`. We may want to explore this in a followup PR (or even this one if people feel strongly that it's strictly required). The same is true for `FromReflect`. We may decide to add an implementation there as well, but it's likely out-of-scope of this PR. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect --all-features ``` --- ## Showcase You can now pass around a `DynamicFunction` as a `dyn PartialReflect`! This also means you can use it as a field on a reflected type without having to ignore it (though you do need to opt out of `FromReflect`). ```rust #[derive(Reflect)] #[reflect(from_reflect = false)] struct ClickEvent { callback: DynamicFunction<'static>, } let event: Box = Box::new(ClickEvent { callback: (|| println!("Clicked!")).into_function(), }); // We can access our `DynamicFunction` as a `dyn PartialReflect` let callback: &dyn PartialReflect = event.field("callback").unwrap(); // And access function-related methods via the new `Function` trait let ReflectRef::Function(callback) = callback.reflect_ref() else { unreachable!() }; // Including calling the function callback.reflect_call(ArgList::new()).unwrap(); // Prints: Clicked! ``` --- benches/benches/bevy_reflect/function.rs | 28 ++- .../bevy_reflect/src/func/dynamic_function.rs | 159 +++++++++++++++++- crates/bevy_reflect/src/func/function.rs | 78 +++++++++ crates/bevy_reflect/src/func/mod.rs | 2 + crates/bevy_reflect/src/lib.rs | 5 +- crates/bevy_reflect/src/reflect.rs | 25 ++- crates/bevy_reflect/src/serde/de/mod.rs | 48 ++++++ crates/bevy_reflect/src/serde/ser/mod.rs | 36 ++++ .../bevy_reflect/src/serde/ser/serializer.rs | 13 +- examples/reflection/reflection_types.rs | 6 + 10 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 crates/bevy_reflect/src/func/function.rs diff --git a/benches/benches/bevy_reflect/function.rs b/benches/benches/bevy_reflect/function.rs index 4eb97eee827b2..cd6e1a75db354 100644 --- a/benches/benches/bevy_reflect/function.rs +++ b/benches/benches/bevy_reflect/function.rs @@ -1,4 +1,4 @@ -use bevy_reflect::func::{ArgList, IntoFunction, TypedFunction}; +use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction}; use bevy_reflect::prelude::*; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; @@ -18,6 +18,11 @@ fn typed(c: &mut Criterion) { let capture = 25; let closure = |a: i32| a + capture; b.iter(|| closure.get_function_info()); + }) + .bench_function("closure_mut", |b| { + let mut capture = 25; + let closure = |a: i32| capture += a; + b.iter(|| closure.get_function_info()); }); } @@ -30,11 +35,23 @@ fn into(c: &mut Criterion) { let capture = 25; let closure = |a: i32| a + capture; b.iter(|| closure.into_function()); + }) + .bench_function("closure_mut", |b| { + let mut _capture = 25; + let closure = move |a: i32| _capture += a; + b.iter(|| closure.into_function_mut()); }); } fn call(c: &mut Criterion) { c.benchmark_group("call") + .bench_function("trait_object", |b| { + b.iter_batched( + || Box::new(add) as Box i32>, + |func| func(75, 25), + BatchSize::SmallInput, + ); + }) .bench_function("function", |b| { let add = add.into_function(); b.iter_batched( @@ -51,6 +68,15 @@ fn call(c: &mut Criterion) { |args| add.call(args), BatchSize::SmallInput, ); + }) + .bench_function("closure_mut", |b| { + let mut capture = 25; + let mut add = (|a: i32| capture += a).into_function_mut(); + b.iter_batched( + || ArgList::new().push_owned(75_i32), + |args| add.call(args), + BatchSize::SmallInput, + ); }); } diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index 2794f1cb9ae89..fba0031489a28 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -1,7 +1,17 @@ +use crate as bevy_reflect; +use crate::__macro_exports::RegisterForReflection; use crate::func::args::{ArgInfo, ArgList}; use crate::func::info::FunctionInfo; -use crate::func::{DynamicFunctionMut, FunctionResult, IntoFunction, IntoFunctionMut, ReturnInfo}; +use crate::func::{ + DynamicFunctionMut, Function, FunctionResult, IntoFunction, IntoFunctionMut, ReturnInfo, +}; +use crate::serde::Serializable; +use crate::{ + ApplyError, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, + ReflectRef, TypeInfo, TypePath, +}; use alloc::borrow::Cow; +use bevy_reflect_derive::impl_type_path; use core::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -136,6 +146,108 @@ impl<'env> DynamicFunction<'env> { } } +impl Function for DynamicFunction<'static> { + fn info(&self) -> &FunctionInfo { + self.info() + } + + fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { + self.call(args) + } + + fn clone_dynamic(&self) -> DynamicFunction<'static> { + self.clone() + } +} + +impl PartialReflect for DynamicFunction<'static> { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + None + } + + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Err(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + None + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + None + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + match value.reflect_ref() { + ReflectRef::Function(func) => { + *self = func.clone_dynamic(); + Ok(()) + } + _ => Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: Self::type_path().into(), + }), + } + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Function + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Function(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Function(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Function(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, _value: &dyn PartialReflect) -> Option { + None + } + + fn debug(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Debug::fmt(self, f) + } + + fn serializable(&self) -> Option { + None + } + + fn is_dynamic(&self) -> bool { + true + } +} + +impl MaybeTyped for DynamicFunction<'static> {} +impl RegisterForReflection for DynamicFunction<'static> {} + +impl_type_path!((in bevy_reflect) DynamicFunction<'env>); + /// Outputs the function's signature. /// /// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. @@ -187,6 +299,7 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunction<'env> { #[cfg(test)] mod tests { use super::*; + use crate::func::IntoReturn; #[test] fn should_overwrite_function_name() { @@ -229,4 +342,48 @@ mod tests { assert_eq!(clone_value, "Hello, world!"); } + + #[test] + fn should_apply_function() { + let mut func: Box = Box::new((|a: i32, b: i32| a + b).into_function()); + func.apply(&((|a: i32, b: i32| a * b).into_function())); + + let args = ArgList::new().push_owned(5_i32).push_owned(5_i32); + let result = func.reflect_call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 25); + } + + #[test] + fn should_allow_recursive_dynamic_function() { + let factorial = DynamicFunction::new( + |mut args| { + let curr = args.pop::()?; + if curr == 0 { + return Ok(1_i32.into_return()); + } + + let arg = args.pop_arg()?; + let this = arg.value(); + + match this.reflect_ref() { + ReflectRef::Function(func) => { + let result = func.reflect_call( + ArgList::new() + .push_ref(this.as_partial_reflect()) + .push_owned(curr - 1), + ); + let value = result.unwrap().unwrap_owned().try_take::().unwrap(); + Ok((curr * value).into_return()) + } + _ => panic!("expected function"), + } + }, + // The `FunctionInfo` doesn't really matter for this test + FunctionInfo::anonymous(), + ); + + let args = ArgList::new().push_ref(&factorial).push_owned(5_i32); + let value = factorial.call(args).unwrap().unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), 120); + } } diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs new file mode 100644 index 0000000000000..f70cfbf9c2804 --- /dev/null +++ b/crates/bevy_reflect/src/func/function.rs @@ -0,0 +1,78 @@ +use crate::func::{ArgList, DynamicFunction, FunctionInfo, FunctionResult}; +use crate::PartialReflect; +use alloc::borrow::Cow; +use core::fmt::Debug; + +/// A trait used to power [function-like] operations via [reflection]. +/// +/// This trait allows types to be called like regular functions +/// with [`Reflect`]-based [arguments] and return values. +/// +/// By default, this trait is currently only implemented for [`DynamicFunction`], +/// however, it is possible to implement this trait for custom function-like types. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{IntoFunction, ArgList, Function}; +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let func: Box = Box::new(add.into_function()); +/// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); +/// let value = func.reflect_call(args).unwrap().unwrap_owned(); +/// assert_eq!(value.try_take::().unwrap(), 100); +/// ``` +/// +/// [function-like]: crate::func +/// [reflection]: crate::Reflect +/// [`Reflect`]: crate::Reflect +/// [arguments]: crate::func::args +/// [`DynamicFunction`]: crate::func::DynamicFunction +pub trait Function: PartialReflect + Debug { + /// The name of the function, if any. + /// + /// For [`DynamicFunctions`] created using [`IntoFunction`], + /// the default name will always be the full path to the function as returned by [`std::any::type_name`], + /// unless the function is a closure, anonymous function, or function pointer, + /// in which case the name will be `None`. + /// + /// [`DynamicFunctions`]: crate::func::DynamicFunction + /// [`IntoFunction`]: crate::func::IntoFunction + fn name(&self) -> Option<&Cow<'static, str>> { + self.info().name() + } + + /// The number of arguments this function accepts. + fn arg_count(&self) -> usize { + self.info().arg_count() + } + + /// The [`FunctionInfo`] for this function. + fn info(&self) -> &FunctionInfo; + + /// Call this function with the given arguments. + fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a>; + + /// Clone this function into a [`DynamicFunction`]. + fn clone_dynamic(&self) -> DynamicFunction<'static>; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::func::IntoFunction; + + #[test] + fn should_call_dyn_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let func: Box = Box::new(add.into_function()); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let value = func.reflect_call(args).unwrap().unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), 100); + } +} diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index 770f0fa0c2633..0a5b1d789598a 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -132,6 +132,7 @@ pub use args::{ArgError, ArgList, ArgValue}; pub use dynamic_function::*; pub use dynamic_function_mut::*; pub use error::*; +pub use function::*; pub use info::*; pub use into_function::*; pub use into_function_mut::*; @@ -144,6 +145,7 @@ pub mod args; mod dynamic_function; mod dynamic_function_mut; mod error; +mod function; mod info; mod into_function; mod into_function_mut; diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index c4326cff6f49c..99451e43f0e41 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -119,10 +119,12 @@ //! * [`Tuple`] //! * [`Array`] //! * [`List`] +//! * [`Set`] //! * [`Map`] //! * [`Struct`] //! * [`TupleStruct`] //! * [`Enum`] +//! * [`Function`] (requires the `functions` feature) //! //! As mentioned previously, the last three are automatically implemented by the [derive macro]. //! @@ -516,6 +518,7 @@ //! [the language feature for dyn upcasting coercion]: https://github.com/rust-lang/rust/issues/65991 //! [derive macro]: derive@crate::Reflect //! [`'static` lifetime]: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html#trait-bound +//! [`Function`]: func::Function //! [derive macro documentation]: derive@crate::Reflect //! [deriving `Reflect`]: derive@crate::Reflect //! [type data]: TypeData @@ -593,7 +596,7 @@ pub mod prelude { }; #[cfg(feature = "functions")] - pub use crate::func::{IntoFunction, IntoFunctionMut}; + pub use crate::func::{Function, IntoFunction, IntoFunctionMut}; } pub use array::*; diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 6538d55c29a3e..3025709a77e8f 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,7 +1,7 @@ use crate::{ - array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, Struct, Tuple, - TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, + array_debug, enum_debug, list_debug, map_debug, serde::Serializable, set_debug, struct_debug, + tuple_debug, tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, + Struct, Tuple, TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, }; use std::{ any::{Any, TypeId}, @@ -26,6 +26,8 @@ macro_rules! impl_reflect_enum { Self::Map(_) => ReflectKind::Map, Self::Set(_) => ReflectKind::Set, Self::Enum(_) => ReflectKind::Enum, + #[cfg(feature = "functions")] + Self::Function(_) => ReflectKind::Function, Self::Value(_) => ReflectKind::Value, } } @@ -42,6 +44,8 @@ macro_rules! impl_reflect_enum { $name::Map(_) => Self::Map, $name::Set(_) => Self::Set, $name::Enum(_) => Self::Enum, + #[cfg(feature = "functions")] + $name::Function(_) => Self::Function, $name::Value(_) => Self::Value, } } @@ -64,6 +68,8 @@ pub enum ReflectRef<'a> { Map(&'a dyn Map), Set(&'a dyn Set), Enum(&'a dyn Enum), + #[cfg(feature = "functions")] + Function(&'a dyn crate::func::Function), Value(&'a dyn PartialReflect), } impl_reflect_enum!(ReflectRef<'_>); @@ -83,6 +89,8 @@ pub enum ReflectMut<'a> { Map(&'a mut dyn Map), Set(&'a mut dyn Set), Enum(&'a mut dyn Enum), + #[cfg(feature = "functions")] + Function(&'a mut dyn crate::func::Function), Value(&'a mut dyn PartialReflect), } impl_reflect_enum!(ReflectMut<'_>); @@ -102,6 +110,8 @@ pub enum ReflectOwned { Map(Box), Set(Box), Enum(Box), + #[cfg(feature = "functions")] + Function(Box), Value(Box), } impl_reflect_enum!(ReflectOwned); @@ -156,6 +166,8 @@ pub enum ReflectKind { Map, Set, Enum, + #[cfg(feature = "functions")] + Function, Value, } @@ -170,6 +182,8 @@ impl std::fmt::Display for ReflectKind { ReflectKind::Map => f.pad("map"), ReflectKind::Set => f.pad("set"), ReflectKind::Enum => f.pad("enum"), + #[cfg(feature = "functions")] + ReflectKind::Function => f.pad("function"), ReflectKind::Value => f.pad("value"), } } @@ -361,8 +375,11 @@ where ReflectRef::List(dyn_list) => list_debug(dyn_list, f), ReflectRef::Array(dyn_array) => array_debug(dyn_array, f), ReflectRef::Map(dyn_map) => map_debug(dyn_map, f), + ReflectRef::Set(dyn_set) => set_debug(dyn_set, f), ReflectRef::Enum(dyn_enum) => enum_debug(dyn_enum, f), - _ => write!(f, "Reflect({})", self.reflect_type_path()), + #[cfg(feature = "functions")] + ReflectRef::Function(dyn_function) => dyn_function.fmt(f), + ReflectRef::Value(_) => write!(f, "Reflect({})", self.reflect_type_path()), } } diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index bc92d3ccf8e8e..4b89803e1d557 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -517,6 +517,54 @@ mod tests { assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); } + #[cfg(feature = "functions")] + mod functions { + use super::*; + use crate::func::DynamicFunction; + + #[test] + fn should_not_deserialize_function() { + #[derive(Reflect)] + #[reflect(from_reflect = false)] + struct MyStruct { + func: DynamicFunction<'static>, + } + + let mut registry = TypeRegistry::new(); + registry.register::(); + + let input = r#"{ + "bevy_reflect::serde::de::tests::functions::MyStruct": ( + func: (), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + + let error = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap_err(); + + #[cfg(feature = "debug_stack")] + assert_eq!( + error, + ron::Error::Message( + "no registration found for type `bevy_reflect::DynamicFunction` (stack: `bevy_reflect::serde::de::tests::functions::MyStruct`)" + .to_string() + ) + ); + + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message( + "no registration found for type `bevy_reflect::DynamicFunction`".to_string() + ) + ); + } + } + #[cfg(feature = "debug_stack")] mod debug_stack { use super::*; diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index ee2a283e82ecb..30d684d4aac92 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -472,6 +472,42 @@ mod tests { ); } + #[cfg(feature = "functions")] + mod functions { + use super::*; + use crate::func::{DynamicFunction, IntoFunction}; + + #[test] + fn should_not_serialize_function() { + #[derive(Reflect)] + #[reflect(from_reflect = false)] + struct MyStruct { + func: DynamicFunction<'static>, + } + + let value: Box = Box::new(MyStruct { + func: String::new.into_function(), + }); + + let registry = TypeRegistry::new(); + let serializer = ReflectSerializer::new(value.as_partial_reflect(), ®istry); + + let error = ron::ser::to_string(&serializer).unwrap_err(); + + #[cfg(feature = "debug_stack")] + assert_eq!( + error, + ron::Error::Message("functions cannot be serialized (stack: `bevy_reflect::serde::ser::tests::functions::MyStruct`)".to_string()) + ); + + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message("functions cannot be serialized".to_string()) + ); + } + } + #[cfg(feature = "debug_stack")] mod debug_stack { use super::*; diff --git a/crates/bevy_reflect/src/serde/ser/serializer.rs b/crates/bevy_reflect/src/serde/ser/serializer.rs index cdc00452aadee..a6c6d56db6f47 100644 --- a/crates/bevy_reflect/src/serde/ser/serializer.rs +++ b/crates/bevy_reflect/src/serde/ser/serializer.rs @@ -154,14 +154,9 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { { #[cfg(feature = "debug_stack")] { - let info = self.value.get_represented_type_info().ok_or_else(|| { - make_custom_error(format_args!( - "type `{}` does not represent any type", - self.value.reflect_type_path(), - )) - })?; - - TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info)); + if let Some(info) = self.value.get_represented_type_info() { + TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info)); + } } // Handle both Value case and types that have a custom `Serialize` @@ -199,6 +194,8 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { ReflectRef::Enum(value) => { EnumSerializer::new(value, self.registry).serialize(serializer) } + #[cfg(feature = "functions")] + ReflectRef::Function(_) => Err(make_custom_error("functions cannot be serialized")), ReflectRef::Value(_) => Err(serializable.err().unwrap()), }; diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index 673092a69448b..ae083995d89f9 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -112,6 +112,12 @@ fn setup() { // This exposes "set" operations on your type, such as getting / inserting by value. // Set is automatically implemented for relevant core types like HashSet ReflectRef::Set(_) => {} + // `Function` is a special trait that can be manually implemented (instead of deriving Reflect). + // This exposes "function" operations on your type, such as calling it with arguments. + // This trait is automatically implemented for types like DynamicFunction. + // This variant only exists if the `reflect_functions` feature is enabled. + #[cfg(feature = "reflect_functions")] + ReflectRef::Function(_) => {} // `Value` types do not implement any of the other traits above. They are simply a Reflect // implementation. Value is implemented for core types like i32, usize, f32, and // String.