From b5b11c2c9e086a6279ded32ae87731582393920f Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Wed, 3 Jul 2024 16:42:11 -0700 Subject: [PATCH 01/10] Remove 'static requirement on IntoReturn references --- .../bevy_reflect/derive/src/impls/func/into_return.rs | 10 +++++----- crates/bevy_reflect/src/func/return_type.rs | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/bevy_reflect/derive/src/impls/func/into_return.rs b/crates/bevy_reflect/derive/src/impls/func/into_return.rs index 02c9fcf67ade1..db964b3cbbe12 100644 --- a/crates/bevy_reflect/derive/src/impls/func/into_return.rs +++ b/crates/bevy_reflect/derive/src/impls/func/into_return.rs @@ -14,19 +14,19 @@ pub(crate) fn impl_into_return( quote! { impl #impl_generics #bevy_reflect::func::IntoReturn for #type_path #ty_generics #where_reflect_clause { - fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> { + fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return { #bevy_reflect::func::Return::Owned(Box::new(self)) } } - impl #impl_generics #bevy_reflect::func::IntoReturn for &'static #type_path #ty_generics #where_reflect_clause { - fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> { + impl #impl_generics #bevy_reflect::func::IntoReturn for &#type_path #ty_generics #where_reflect_clause { + fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return { #bevy_reflect::func::Return::Ref(self) } } - impl #impl_generics #bevy_reflect::func::IntoReturn for &'static mut #type_path #ty_generics #where_reflect_clause { - fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> { + impl #impl_generics #bevy_reflect::func::IntoReturn for &mut #type_path #ty_generics #where_reflect_clause { + fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return { #bevy_reflect::func::Return::Mut(self) } } diff --git a/crates/bevy_reflect/src/func/return_type.rs b/crates/bevy_reflect/src/func/return_type.rs index b9e94dca635ae..3e426350ef18b 100644 --- a/crates/bevy_reflect/src/func/return_type.rs +++ b/crates/bevy_reflect/src/func/return_type.rs @@ -68,7 +68,9 @@ impl<'a> Return<'a> { /// [derive macro]: derive@crate::Reflect pub trait IntoReturn { /// Converts [`Self`] into a [`Return`] value. - fn into_return<'a>(self) -> Return<'a>; + fn into_return<'a>(self) -> Return<'a> + where + Self: 'a; } impl IntoReturn for () { @@ -111,7 +113,7 @@ macro_rules! impl_into_return { $($U $(: $U1 $(+ $U2)*)?),* )? { - fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> { + fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return { $crate::func::Return::Owned(Box::new(self)) } } @@ -125,7 +127,7 @@ macro_rules! impl_into_return { $($U $(: $U1 $(+ $U2)*)?),* )? { - fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> { + fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return { $crate::func::Return::Ref(self) } } @@ -139,7 +141,7 @@ macro_rules! impl_into_return { $($U $(: $U1 $(+ $U2)*)?),* )? { - fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> { + fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return { $crate::func::Return::Mut(self) } } From 9a4ae418ac8d54736beec6401a0f5dc7c05ffdef Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 4 Jul 2024 09:58:13 -0700 Subject: [PATCH 02/10] Add DynamicClosure Also adds ReflectFn, ReflectFnMut, TypedFunction, and IntoClosure --- crates/bevy_reflect/src/func/closure.rs | 185 ++++++++++ crates/bevy_reflect/src/func/function.rs | 80 +++-- crates/bevy_reflect/src/func/info.rs | 184 +++++++++- crates/bevy_reflect/src/func/into_closure.rs | 32 ++ crates/bevy_reflect/src/func/into_function.rs | 327 ++---------------- crates/bevy_reflect/src/func/macros.rs | 13 + crates/bevy_reflect/src/func/mod.rs | 121 ++++++- crates/bevy_reflect/src/func/reflect_fn.rs | 223 ++++++++++++ .../bevy_reflect/src/func/reflect_fn_mut.rs | 233 +++++++++++++ 9 files changed, 1033 insertions(+), 365 deletions(-) create mode 100644 crates/bevy_reflect/src/func/closure.rs create mode 100644 crates/bevy_reflect/src/func/into_closure.rs create mode 100644 crates/bevy_reflect/src/func/reflect_fn.rs create mode 100644 crates/bevy_reflect/src/func/reflect_fn_mut.rs diff --git a/crates/bevy_reflect/src/func/closure.rs b/crates/bevy_reflect/src/func/closure.rs new file mode 100644 index 0000000000000..24bf10bb5cb09 --- /dev/null +++ b/crates/bevy_reflect/src/func/closure.rs @@ -0,0 +1,185 @@ +use alloc::borrow::Cow; +use core::fmt::{Debug, Formatter}; + +use crate::func::args::{ArgInfo, ArgList}; +use crate::func::info::FunctionInfo; +use crate::func::into_closure::IntoClosure; +use crate::func::{FunctionResult, ReturnInfo}; + +/// A dynamic representation of a Rust closure. +/// +/// For our purposes, a "closure" is just a callable that may reference its environment. +/// This includes any type of Rust function or closure. +/// +/// This type can be seen as a superset of [`DynamicFunction`]. +/// +/// See the [module-level documentation] for more information. +/// +/// You will generally not need to construct this manually. +/// Instead, many functions and closures can be automatically converted using the [`IntoClosure`] trait. +/// +/// # Example +/// +/// Most of the time, a [`DynamicClosure`] can be created using the [`IntoClosure`] trait: +/// +/// ``` +/// # use bevy_reflect::func::{ArgList, DynamicClosure, FunctionInfo, IntoClosure}; +/// # +/// let mut list: Vec = vec![1, 2, 3]; +/// +/// // `replace` is a closure that captures a mutable reference to `list` +/// let mut replace = |index: usize, value: i32| -> i32 { +/// let old_value = list[index]; +/// list[index] = value; +/// old_value +/// }; +/// +/// // Convert the closure into a dynamic closure using `IntoClosure::into_closure` +/// let mut func: DynamicClosure = replace.into_closure(); +/// +/// // Dynamically call the closure: +/// let args = ArgList::default().push_owned(1_usize).push_owned(-2_i32); +/// let value = func.call(args).unwrap().unwrap_owned(); +/// +/// // Check the result: +/// assert_eq!(value.take::().unwrap(), 2); +/// +/// // Note that `func` still has a reference to `list`, +/// // so we need to drop it before we can access `list` again. +/// drop(func); +/// assert_eq!(list, vec![1, -2, 3]); +/// ``` +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +pub struct DynamicClosure<'env> { + info: FunctionInfo, + func: Box FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>, +} + +impl<'env> DynamicClosure<'env> { + /// Create a new [`DynamicClosure`]. + /// + /// The given function can be used to call out to a regular function, closure, or method. + /// + /// It's important that the closure signature matches the provided [`FunctionInfo`]. + /// This info is used to validate the arguments and return value. + pub fn new FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>( + func: F, + info: FunctionInfo, + ) -> Self { + Self { + info, + func: Box::new(func), + } + } + + /// Set the name of the closure. + /// + /// For [`DynamicClosures`] created using [`IntoClosure`], + /// the default name will always be the full path to the closure as returned by [`std::any::type_name`]. + /// + /// This default name generally does not contain the actual name of the closure, only its module path. + /// It is therefore recommended to set the name manually using this method. + /// + /// [`DynamicClosures`]: DynamicClosure + pub fn with_name(mut self, name: impl Into>) -> Self { + self.info = self.info.with_name(name); + self + } + + /// Set the arguments of the closure. + /// + /// It is very important that the arguments match the intended closure signature, + /// as this is used to validate arguments passed to the closure. + pub fn with_args(mut self, args: Vec) -> Self { + self.info = self.info.with_args(args); + self + } + + /// Set the return information of the closure. + pub fn with_return_info(mut self, return_info: ReturnInfo) -> Self { + self.info = self.info.with_return_info(return_info); + self + } + + /// Call the closure with the given arguments. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{IntoClosure, ArgList}; + /// let add = |a: i32, b: i32| -> i32 { + /// a + b + /// }; + /// + /// let mut func = add.into_closure().with_name("add"); + /// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + /// let result = func.call(args).unwrap().unwrap_owned(); + /// assert_eq!(result.take::().unwrap(), 100); + /// ``` + pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { + (self.func)(args, &self.info) + } + + /// Call the closure with the given arguments and consume the closure. + /// + /// This is useful for closures that capture their environment because otherwise + /// any captured variables would still be borrowed by this closure. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{IntoClosure, ArgList}; + /// let mut count = 0; + /// let increment = |amount: i32| count += amount; + /// + /// let increment_function = increment.into_closure(); + /// let args = ArgList::new().push_owned(5_i32); + /// + /// // We need to drop `increment_function` here so that we + /// // can regain access to `count`. + /// // `call_once` does this automatically for us. + /// increment_function.call_once(args).unwrap(); + /// assert_eq!(count, 5); + /// ``` + pub fn call_once(mut self, args: ArgList) -> FunctionResult { + (self.func)(args, &self.info) + } + + /// Returns the closure info. + pub fn info(&self) -> &FunctionInfo { + &self.info + } +} + +/// Outputs the closure's signature. +/// +/// This takes the format: `DynamicClosure(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. +/// +/// Names for arguments and the closure itself are optional and will default to `_` if not provided. +impl<'env> Debug for DynamicClosure<'env> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let name = self.info.name().unwrap_or("_"); + write!(f, "DynamicClosure(fn {name}(")?; + + for (index, arg) in self.info.args().iter().enumerate() { + let name = arg.name().unwrap_or("_"); + let ty = arg.type_path(); + write!(f, "{name}: {ty}")?; + + if index + 1 < self.info.args().len() { + write!(f, ", ")?; + } + } + + let ret = self.info.return_info().type_path(); + write!(f, ") -> {ret})") + } +} + +impl<'env> IntoClosure<'env, ()> for DynamicClosure<'env> { + #[inline] + fn into_closure(self) -> DynamicClosure<'env> { + self + } +} diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index 6ac4b1bbf8d85..38b60cfb8eb2d 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -1,11 +1,12 @@ +use alloc::borrow::Cow; +use core::fmt::{Debug, Formatter}; +use std::sync::Arc; + use crate::func::args::{ArgInfo, ArgList}; use crate::func::error::FunctionError; use crate::func::info::FunctionInfo; use crate::func::return_type::Return; use crate::func::{IntoFunction, ReturnInfo}; -use alloc::borrow::Cow; -use core::fmt::{Debug, Formatter}; -use std::ops::DerefMut; /// The result of calling a dynamic [`DynamicFunction`]. /// @@ -15,7 +16,16 @@ pub type FunctionResult<'a> = Result, FunctionError>; /// A dynamic representation of a Rust function. /// -/// Internally this stores a function pointer and associated info. +/// For our purposes, a "function" is just a callable that may not reference its environment. +/// +/// This includes: +/// - Functions and methods defined with the `fn` keyword +/// - Closures that do not capture their environment +/// - Closures that take ownership of captured variables +/// +/// To handle closures that capture references to their environment, see [`DynamicClosure`]. +/// +/// See the [module-level documentation] for more information. /// /// You will generally not need to construct this manually. /// Instead, many functions and closures can be automatically converted using the [`IntoFunction`] trait. @@ -90,25 +100,28 @@ pub type FunctionResult<'a> = Result, FunctionError>; /// // Check the result: /// assert_eq!(list, vec!["Hello, World!!!"]); /// ``` -pub struct DynamicFunction<'env> { +/// +/// [`DynamicClosure`]: crate::func::DynamicClosure +/// [module-level documentation]: crate::func +pub struct DynamicFunction { info: FunctionInfo, - func: Box FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>, + func: Arc Fn(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'static>, } -impl<'env> DynamicFunction<'env> { +impl DynamicFunction { /// Create a new dynamic [`DynamicFunction`]. /// /// The given function can be used to call out to a regular function, closure, or method. /// /// It's important that the function signature matches the provided [`FunctionInfo`]. /// This info is used to validate the arguments and return value. - pub fn new FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>( + pub fn new Fn(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'static>( func: F, info: FunctionInfo, ) -> Self { Self { info, - func: Box::new(func), + func: Arc::new(func), } } @@ -144,41 +157,17 @@ impl<'env> DynamicFunction<'env> { /// /// ``` /// # use bevy_reflect::func::{IntoFunction, ArgList}; - /// fn add(left: i32, right: i32) -> i32 { - /// left + right + /// fn add(a: i32, b: i32) -> i32 { + /// a + b /// } /// - /// let mut func = add.into_function(); + /// let func = add.into_function(); /// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); /// let result = func.call(args).unwrap().unwrap_owned(); /// assert_eq!(result.take::().unwrap(), 100); /// ``` - pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { - (self.func.deref_mut())(args, &self.info) - } - - /// Call the function with the given arguments and consume the function. - /// - /// This is useful for closures that capture their environment because otherwise - /// any captured variables would still be borrowed by this function. - /// - /// # Example - /// - /// ``` - /// # use bevy_reflect::func::{IntoFunction, ArgList}; - /// let mut count = 0; - /// let increment = |amount: i32| { - /// count += amount; - /// }; - /// let increment_function = increment.into_function(); - /// let args = ArgList::new().push_owned(5_i32); - /// // We need to drop `increment_function` here so that we - /// // can regain access to `count`. - /// increment_function.call_once(args).unwrap(); - /// assert_eq!(count, 5); - /// ``` - pub fn call_once(mut self, args: ArgList) -> FunctionResult { - (self.func.deref_mut())(args, &self.info) + pub fn call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { + (self.func)(args, &self.info) } /// Returns the function info. @@ -192,7 +181,7 @@ impl<'env> DynamicFunction<'env> { /// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// /// Names for arguments and the function itself are optional and will default to `_` if not provided. -impl<'env> Debug for DynamicFunction<'env> { +impl Debug for DynamicFunction { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let name = self.info.name().unwrap_or("_"); write!(f, "DynamicFunction(fn {name}(")?; @@ -212,9 +201,18 @@ impl<'env> Debug for DynamicFunction<'env> { } } -impl<'env> IntoFunction<'env, ()> for DynamicFunction<'env> { +impl Clone for DynamicFunction { + fn clone(&self) -> Self { + Self { + info: self.info.clone(), + func: Arc::clone(&self.func), + } + } +} + +impl IntoFunction<()> for DynamicFunction { #[inline] - fn into_function(self) -> DynamicFunction<'env> { + fn into_function(self) -> DynamicFunction { self } } diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 49c0789c58551..8e0bccd69df77 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -1,6 +1,9 @@ +use alloc::borrow::Cow; + +use bevy_utils::all_tuples; + use crate::func::args::{ArgInfo, GetOwnership, Ownership}; use crate::TypePath; -use alloc::borrow::Cow; /// Type information for a [`DynamicFunction`]. /// @@ -24,6 +27,14 @@ impl FunctionInfo { } } + /// Create a new [`FunctionInfo`] from the given function or closure. + pub fn from(function: &F) -> Self + where + F: TypedFunction, + { + function.get_function_info() + } + /// Set the name of the function. /// /// Reflected functions are not required to have a name, @@ -111,3 +122,174 @@ impl ReturnInfo { self.ownership } } + +/// A static accessor to compile-time type information for functions. +/// +/// This is the equivalent of [`Typed`] for functions. +/// +/// # Blanket Implementation +/// +/// This trait has a blanket implementation that covers: +/// - Functions and methods defined with the `fn` keyword +/// - Closures that do not capture their environment +/// - Closures that capture immutable references to their environment +/// - Closures that capture mutable references to their environment +/// - Closures that take ownership of captured variables +/// +/// For each of the above cases, the function signature may only have up to 15 arguments, +/// not including an optional receiver argument (often `&self` or `&mut self`). +/// This optional receiver argument may be either a mutable or immutable reference to a type. +/// If the return type is also a reference, its lifetime will be bound to the lifetime of this receiver. +/// +/// See the [module-level documentation] for more information on valid signatures. +/// +/// Arguments and the return type are expected to implement both [`GetOwnership`] and [`TypePath`]. +/// By default, these traits are automatically implemented when using the `Reflect` [derive macro]. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{ArgList, FunctionInfo, ReflectFnMut, TypedFunction}; +/// # +/// fn print(value: String) { +/// println!("{}", value); +/// } +/// +/// let info = print.get_function_info(); +/// assert!(info.name().unwrap().ends_with("print")); +/// assert_eq!(info.arg_count(), 1); +/// assert_eq!(info.args()[0].type_path(), "alloc::string::String"); +/// assert_eq!(info.return_info().type_path(), "()"); +/// ``` +/// +/// # Trait Parameters +/// +/// This trait has a `Marker` type parameter that is used to get around issues with +/// [unconstrained type parameters] when defining impls with generic arguments or return types. +/// This `Marker` can be any type, provided it doesn't conflict with other implementations. +/// +/// [`Typed`]: crate::Typed +pub trait TypedFunction { + /// Get the [`FunctionInfo`] for this type. + fn function_info() -> FunctionInfo; + + /// Get the [`FunctionInfo`] for this type. + fn get_function_info(&self) -> FunctionInfo { + Self::function_info() + } +} + +/// Helper macro for implementing [`TypedFunction`] on Rust closures. +/// +/// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): +/// - `fn(arg0, arg1, ..., argN) -> R` +/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +macro_rules! impl_typed_function { + ($(($Arg:ident, $arg:ident)),*) => { + // === (...) -> ReturnType === // + impl<$($Arg,)* ReturnType, Function> TypedFunction [ReturnType]> for Function + where + $($Arg: TypePath + GetOwnership,)* + ReturnType: TypePath + GetOwnership, + Function: FnMut($($Arg),*) -> ReturnType, + { + fn function_info() -> FunctionInfo { + FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 0; + vec![ + $(ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::()) + } + } + + // === (&self, ...) -> &ReturnType === // + impl TypedFunction &ReturnType> for Function + where + for<'a> &'a Receiver: TypePath + GetOwnership, + $($Arg: TypePath + GetOwnership,)* + for<'a> &'a ReturnType: TypePath + GetOwnership, + Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType, + { + fn function_info() -> $crate::func::FunctionInfo { + FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + ArgInfo::new::<&Receiver>(0), + $($crate::func::args::ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::<&ReturnType>()) + } + } + + // === (&mut self, ...) -> &mut ReturnType === // + impl TypedFunction &mut ReturnType> for Function + where + for<'a> &'a mut Receiver: TypePath + GetOwnership, + $($Arg: TypePath + GetOwnership,)* + for<'a> &'a mut ReturnType: TypePath + GetOwnership, + Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType, + { + fn function_info() -> FunctionInfo { + FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + ArgInfo::new::<&mut Receiver>(0), + $(ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::<&mut ReturnType>()) + } + } + + // === (&mut self, ...) -> &ReturnType === // + impl TypedFunction &ReturnType> for Function + where + for<'a> &'a mut Receiver: TypePath + GetOwnership, + $($Arg: TypePath + GetOwnership,)* + for<'a> &'a ReturnType: TypePath + GetOwnership, + Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType, + { + fn function_info() -> FunctionInfo { + FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + ArgInfo::new::<&mut Receiver>(0), + $(ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::<&ReturnType>()) + } + } + }; +} + +all_tuples!(impl_typed_function, 0, 15, Arg, arg); diff --git a/crates/bevy_reflect/src/func/into_closure.rs b/crates/bevy_reflect/src/func/into_closure.rs new file mode 100644 index 0000000000000..439c24b0746f4 --- /dev/null +++ b/crates/bevy_reflect/src/func/into_closure.rs @@ -0,0 +1,32 @@ +use crate::func::closure::DynamicClosure; +use crate::func::{ReflectFnMut, TypedFunction}; + +/// A trait for types that can be converted into a [`DynamicClosure`]. +/// +/// This trait is automatically implemented for any type that implements +/// [`ReflectFnMut`] and [`TypedFunction`]. +/// +/// Because [`ReflectFn`] is a subtrait of [`ReflectFnMut`], +/// this trait can be seen as a supertrait of [`IntoFunction`]. +/// +/// See the [module-level documentation] for more information. +/// +/// [`ReflectFn`]: crate::func::ReflectFn +/// [`IntoFunction`]: crate::func::IntoFunction +/// [module-level documentation]: crate::func +pub trait IntoClosure<'env, Marker> { + /// Converts [`Self`] into a [`DynamicClosure`]. + fn into_closure(self) -> DynamicClosure<'env>; +} + +impl<'env, F, Marker1, Marker2> IntoClosure<'env, (Marker1, Marker2)> for F +where + F: ReflectFnMut<'env, Marker1> + TypedFunction + 'env, +{ + fn into_closure(mut self) -> DynamicClosure<'env> { + DynamicClosure::new( + move |args, info| self.reflect_call_mut(args, info), + Self::function_info(), + ) + } +} diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index 30609c948ccfd..0f128bda24487 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -1,318 +1,31 @@ use crate::func::function::DynamicFunction; -use bevy_utils::all_tuples; +use crate::func::{ReflectFn, TypedFunction}; /// A trait for types that can be converted into a [`DynamicFunction`]. /// -/// # Blanket Implementation +/// This trait is automatically implemented for any type with a `'static` lifetime +/// and also implements [`ReflectFn`] and [`TypedFunction`]. /// -/// This trait has a blanket implementation that covers many functions, closures, and methods. -/// And though it works for many cases, it does have some limitations. +/// To handle types such as closures that capture references to their environment, +/// see [`IntoClosure`] instead. /// -/// ## Arguments +/// See the [module-level documentation] for more information. /// -/// Firstly, the function signature may only have up to 15 arguments -/// (or 16 if the first argument is a mutable/immutable reference). -/// This limitation is unfortunately due to the [lack of variadic generics] in Rust. -/// -/// Each argument must implement [`FromArg`], [`GetOwnership`], and [`TypePath`]. -/// -/// -/// ```compile_fail -/// # use bevy_reflect::func::IntoFunction; -/// fn too_many_args( -/// arg01: i32, -/// arg02: i32, -/// arg03: i32, -/// arg04: i32, -/// arg05: i32, -/// arg06: i32, -/// arg07: i32, -/// arg08: i32, -/// arg09: i32, -/// arg10: i32, -/// arg11: i32, -/// arg12: i32, -/// arg13: i32, -/// arg14: i32, -/// arg15: i32, -/// arg16: i32, -/// ) { -/// // ... -/// } -/// -/// // This will fail to compile: -/// too_many_args.into_function(); -/// ``` -/// -/// ## Return Type -/// -/// Secondly, the allowed return type is dependent on the first argument of the function: -/// - If the first argument is an immutable reference, -/// then the return type may be either an owned type, a static reference type, or a reference type -/// bound to the lifetime of the first argument. -/// - If the first argument is a mutable reference, -/// then the return type may be either an owned type, a static reference type, or be a mutable reference type -/// bound to the lifetime of the first argument. -/// - If the first argument is an owned type, -/// then the return type may be either an owned type or a static reference type. -/// -/// The return type must always implement [`GetOwnership`] and [`TypePath`]. -/// If it is either an owned type or a static reference type, -/// then it must also implement [`IntoReturn`]. -/// Otherwise, it must also implement [`Reflect`]. -/// -/// Note that both `GetOwnership`, `TypePath`, and `IntoReturn` are automatically implemented -/// when [deriving `Reflect`]. -/// -/// ``` -/// # use bevy_reflect::func::IntoFunction; -/// fn owned_return(arg: i32) -> i32 { arg * 2 } -/// fn ref_return(arg: &i32) -> &i32 { arg } -/// fn mut_return(arg: &mut i32) -> &mut i32 { arg } -/// fn static_return(arg: i32) -> &'static i32 { &123 } -/// -/// owned_return.into_function(); -/// ref_return.into_function(); -/// mut_return.into_function(); -/// static_return.into_function(); -/// ``` -/// -/// [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ -/// [`FromArg`]: crate::func::args::FromArg -/// [`GetOwnership`]: crate::func::args::GetOwnership -/// [`TypePath`]: crate::TypePath -/// [`IntoReturn`]: crate::func::IntoReturn -/// [`Reflect`]: crate::Reflect -/// [deriving `Reflect`]: derive@crate::Reflect -pub trait IntoFunction<'env, Marker> { +/// [`IntoClosure`]: crate::func::IntoClosure +/// [module-level documentation]: crate::func +pub trait IntoFunction { /// Converts [`Self`] into a [`DynamicFunction`]. - fn into_function(self) -> DynamicFunction<'env>; -} - -/// Helper macro that returns the number of tokens it receives. -/// -/// This is used to get the argument count. -/// -/// See [here] for details. -/// -/// [here]: https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html#bit-twiddling -macro_rules! count_tts { - () => { 0 }; - ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 }; - ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 }; + fn into_function(self) -> DynamicFunction; } -/// Helper macro for implementing [`IntoFunction`] on Rust functions. -/// -/// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): -/// - `fn(arg0, arg1, ..., argN) -> R` -/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` -macro_rules! impl_into_function { - ($(($Arg:ident, $arg:ident)),*) => { - // === Owned Return === // - impl<'env, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn($($Arg),*) -> R> for F - where - $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* - R: $crate::func::IntoReturn + $crate::func::args::GetOwnership + $crate::TypePath, - F: FnMut($($Arg),*) -> R + 'env, - F: for<'a> FnMut($($Arg::Item<'a>),*) -> R + 'env, - { - fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { - const COUNT: usize = count_tts!($($Arg)*); - - let info = $crate::func::FunctionInfo::new() - .with_name(std::any::type_name::()) - .with_args({ - #[allow(unused_mut)] - let mut _index = 0; - vec![ - $($crate::func::args::ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info($crate::func::ReturnInfo::new::()); - - $crate::func::DynamicFunction::new(move |args, _info| { - if args.len() != COUNT { - return Err($crate::func::error::FunctionError::InvalidArgCount { - expected: COUNT, - received: args.len(), - }); - } - - let [$($arg,)*] = args.take().try_into().expect("invalid number of arguments"); - - #[allow(unused_mut)] - let mut _index = 0; - let ($($arg,)*) = ($($Arg::from_arg($arg, { - _index += 1; - _info.args().get(_index - 1).expect("argument index out of bounds") - })?,)*); - Ok((self)($($arg,)*).into_return()) - }, info) - } - } - - // === Ref Receiver + Ref Return === // - impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&Receiver, $($Arg),*) -> fn(&R)> for F - where - Receiver: $crate::Reflect + $crate::TypePath, - for<'a> &'a Receiver: $crate::func::args::GetOwnership, - R: $crate::Reflect + $crate::TypePath, - for<'a> &'a R: $crate::func::args::GetOwnership, - $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* - F: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a R + 'env, - F: for<'a> FnMut(&'a Receiver, $($Arg::Item<'a>),*) -> &'a R + 'env, - { - fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); - - let info = $crate::func::FunctionInfo::new() - .with_name(std::any::type_name::()) - .with_args({ - #[allow(unused_mut)] - let mut _index = 1; - vec![ - $crate::func::args::ArgInfo::new::<&Receiver>(0), - $($crate::func::args::ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info($crate::func::ReturnInfo::new::<&R>()); - - $crate::func::DynamicFunction::new(move |args, _info| { - if args.len() != COUNT { - return Err($crate::func::error::FunctionError::InvalidArgCount { - expected: COUNT, - received: args.len(), - }); - } - - let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); - - let receiver = receiver.take_ref::(_info.args().get(0).expect("argument index out of bounds"))?; - - #[allow(unused_mut)] - let mut _index = 1; - let ($($arg,)*) = ($($Arg::from_arg($arg, { - _index += 1; - _info.args().get(_index - 1).expect("argument index out of bounds") - })?,)*); - Ok($crate::func::Return::Ref((self)(receiver, $($arg,)*))) - }, info) - } - } - - // === Mut Receiver + Mut Return === // - impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&mut Receiver, $($Arg),*) -> fn(&mut R)> for F - where - Receiver: $crate::Reflect + $crate::TypePath, - for<'a> &'a mut Receiver: $crate::func::args::GetOwnership, - R: $crate::Reflect + $crate::TypePath, - for<'a> &'a mut R: $crate::func::args::GetOwnership, - $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* - F: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut R + 'env, - F: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut R + 'env, - { - fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); - - let info = $crate::func::FunctionInfo::new() - .with_name(std::any::type_name::()) - .with_args({ - #[allow(unused_mut)] - let mut _index = 1; - vec![ - $crate::func::args::ArgInfo::new::<&mut Receiver>(0), - $($crate::func::args::ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info($crate::func::ReturnInfo::new::<&mut R>()); - - $crate::func::DynamicFunction::new(move |args, _info| { - if args.len() != COUNT { - return Err($crate::func::error::FunctionError::InvalidArgCount { - expected: COUNT, - received: args.len(), - }); - } - - let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); - - let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; - - #[allow(unused_mut)] - let mut _index = 1; - let ($($arg,)*) = ($($Arg::from_arg($arg, { - _index += 1; - _info.args().get(_index - 1).expect("argument index out of bounds") - })?,)*); - Ok($crate::func::Return::Mut((self)(receiver, $($arg,)*))) - }, info) - } - } - - // === Mut Receiver + Ref Return === // - impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&mut Receiver, $($Arg),*) -> fn(&mut R) -> &R> for F - where - Receiver: $crate::Reflect + $crate::TypePath, - for<'a> &'a mut Receiver: $crate::func::args::GetOwnership, - R: $crate::Reflect + $crate::TypePath, - for<'a> &'a mut R: $crate::func::args::GetOwnership, - $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* - F: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a R + 'env, - F: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a R + 'env, - { - fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); - - let info = $crate::func::FunctionInfo::new() - .with_name(std::any::type_name::()) - .with_args({ - #[allow(unused_mut)] - let mut _index = 1; - vec![ - $crate::func::args::ArgInfo::new::<&mut Receiver>(0), - $($crate::func::args::ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info($crate::func::ReturnInfo::new::<&mut R>()); - - $crate::func::DynamicFunction::new(move |args, _info| { - if args.len() != COUNT { - return Err($crate::func::error::FunctionError::InvalidArgCount { - expected: COUNT, - received: args.len(), - }); - } - - let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); - - let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; - - #[allow(unused_mut)] - let mut _index = 1; - let ($($arg,)*) = ($($Arg::from_arg($arg, { - _index += 1; - _info.args().get(_index - 1).expect("argument index out of bounds") - })?,)*); - Ok($crate::func::Return::Ref((self)(receiver, $($arg,)*))) - }, info) - } - } - }; +impl IntoFunction<(Marker1, Marker2)> for F +where + F: ReflectFn<'static, Marker1> + TypedFunction + 'static, +{ + fn into_function(self) -> DynamicFunction { + DynamicFunction::new( + move |args, info| self.reflect_call(args, info), + Self::function_info(), + ) + } } - -all_tuples!(impl_into_function, 0, 15, Arg, arg); diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs index a6be3850e3d1e..0787a1eb83d70 100644 --- a/crates/bevy_reflect/src/func/macros.rs +++ b/crates/bevy_reflect/src/func/macros.rs @@ -97,3 +97,16 @@ macro_rules! impl_function_traits { } pub(crate) use impl_function_traits; + +/// Helper macro that returns the number of tokens it receives. +/// +/// See [here] for details. +/// +/// [here]: https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html#bit-twiddling +macro_rules! count_tts { + () => { 0 }; + ($odd:tt $($a:tt $b:tt)*) => { ($crate::func::macros::count_tts!($($a)*) << 1) | 1 }; + ($($a:tt $even:tt)*) => { $crate::func::macros::count_tts!($($a)*) << 1 }; +} + +pub(crate) use count_tts; diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index a964cf60f7a22..f7a966095dbc9 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -1,12 +1,12 @@ //! Reflection-based dynamic functions. //! //! This module provides a way to pass around and call functions dynamically -//! using the [`DynamicFunction`] type. +//! using the [`DynamicFunction`] and [`DynamicClosure`] types. //! -//! Many simple functions and closures can be automatically converted to [`DynamicFunction`] -//! using the [`IntoFunction`] trait. +//! Many simple functions can be automatically converted to [`DynamicFunction`] or [`DynamicClosure`] +//! using the [`IntoFunction`] or [`IntoClosure`] traits. //! -//! Once the [`DynamicFunction`] is created, it can be called with a set of arguments provided +//! Once this dynamic representation is created, it can be called with a set of arguments provided //! via an [`ArgList`]. //! //! This returns a [`FunctionResult`] containing the [`Return`] value, @@ -34,22 +34,81 @@ //! assert_eq!(value.unwrap_owned().downcast_ref::(), Some(&100)); //! ``` //! +//! # Function vs Closure +//! +//! In Rust, a "function" is any callable that does not capture its environment. +//! These are typically defined with the `fn` keyword, but may also use anonymous function syntax. +//! +//! Rust also has the concept of "closures", which are functions that capture their environment. +//! These are always defined with anonymous function syntax. +//! +//! For the purposes of reflection, this module alters those definitions a bit. +//! Rather than placing the distinction on whether the callable _captures_ its environment, +//! we place it on whether it _references_ its environment. +//! +//! This means that a "reflected function" is a function or closure that either does not +//! capture any variables or takes ownership of all captured variables. +//! In other words, it has a `'static` lifetime. +//! +//! A "reflected closure", on the other hand, is a closure that captures references to its environment. +//! +//! This difference from the Rust definitions exists in order to make reflected functions more flexible. +//! +//! An example of where this might be useful is creating a [`DynamicFunction`] for a scripting language, +//! where you want to inject some runtime state into the function without changing its signature for consumers. +//! By Rust's definitions, this would only be possible for closures. +//! And while this would still be defined as a closure at compile time, +//! it would be seen by users of the scripting language as a function at runtime. +//! +//! # Valid Signatures +//! +//! Many of the traits in this module have default blanket implementations over a specific set of function signatures. +//! +//! These signatures are: +//! - `fn(...) -> R` +//! - `fn<'a>(&'a arg, ...) -> &'a R` +//! - `fn<'a>(&'a mut arg, ...) -> &'a R` +//! - `fn<'a>(&'a mut arg, ...) -> &'a mut R` +//! +//! Where `...` represents 0 to 15 arguments (inclusive) of the form `T`, `&T`, or `&mut T`. +//! The lifetime of any reference to the return type `R`, must be tied to a "receiver" argument +//! (i.e. the first argument in the signature, normally `self`). +//! +//! Each trait will also have its own requirements for what traits are required for both arguments and return types, +//! but a good rule-of-thumb is that all types should derive [`Reflect`]. +//! +//! The reason for such a small subset of valid signatures is due to limitations in Rust— +//! namely the [lack of variadic generics] and certain [coherence issues]. +//! +//! For other functions that don't conform to one of the above signatures, +//! [`DynamicFunction`] and [`DynamicClosure`] can instead be created manually. +//! //! [`Reflect`]: crate::Reflect +//! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ +//! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check +pub use closure::*; pub use error::*; pub use function::*; pub use info::*; +pub use into_closure::*; pub use into_function::*; +pub use reflect_fn::*; +pub use reflect_fn_mut::*; pub use return_type::*; pub use args::{Arg, ArgError, ArgList}; pub mod args; +mod closure; mod error; mod function; mod info; +mod into_closure; mod into_function; pub(crate) mod macros; +mod reflect_fn; +mod reflect_fn_mut; mod return_type; #[cfg(test)] @@ -66,20 +125,50 @@ mod tests { a + b } - let mut func = add.into_function(); + let func = add.into_function(); let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); let result = func.call(args).unwrap().unwrap_owned(); assert_eq!(result.downcast_ref::(), Some(&100)); } + #[test] + fn should_create_dynamic_function_from_closure() { + let c = 23; + let func = (move |a: i32, b: i32| a + b + c).into_function(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&123)); + } + #[test] fn should_create_dynamic_closure() { - let mut func = (|a: i32, b: i32| a + b).into_function(); + let mut func = (|a: i32, b: i32| a + b).into_closure(); let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); let result = func.call(args).unwrap().unwrap_owned(); assert_eq!(result.downcast_ref::(), Some(&100)); } + #[test] + fn should_create_dynamic_closure_from_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let mut func = add.into_closure(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&100)); + } + + #[test] + fn should_create_dynamic_closure_with_capture() { + let mut total = 0; + let func = (|a: i32, b: i32| total = a + b).into_closure(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + func.call_once(args).unwrap(); + assert_eq!(total, 100); + } + #[test] fn should_create_dynamic_method() { #[derive(Reflect, Debug, PartialEq)] @@ -94,7 +183,7 @@ mod tests { let foo_a = Foo(25); let foo_b = Foo(75); - let mut func = Foo::add.into_function(); + let func = Foo::add.into_function(); let args = ArgList::new().push_ref(&foo_a).push_ref(&foo_b); let result = func.call(args).unwrap().unwrap_owned(); assert_eq!(result.downcast_ref::(), Some(&Foo(100))); @@ -106,7 +195,7 @@ mod tests { String::from("Hello, World!") } - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new(); let result = func.call(args).unwrap().unwrap_owned(); assert_eq!( @@ -119,7 +208,7 @@ mod tests { fn should_allow_unit_return() { fn foo(_: i32) {} - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new().push_owned(123_i32); let result = func.call(args).unwrap(); assert!(result.is_unit()); @@ -132,7 +221,7 @@ mod tests { } let value: i32 = 123; - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new() .push_ref(&value) .push_owned(String::from("Hello, World!")) @@ -148,7 +237,7 @@ mod tests { } let mut value: i32 = 123; - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new() .push_mut(&mut value) .push_owned(String::from("Hello, World!")) @@ -191,7 +280,7 @@ mod tests { fn should_error_on_missing_args() { fn foo(_: i32) {} - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new(); let result = func.call(args); assert_eq!( @@ -207,7 +296,7 @@ mod tests { fn should_error_on_too_many_args() { fn foo() {} - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new().push_owned(123_i32); let result = func.call(args); assert_eq!( @@ -223,7 +312,7 @@ mod tests { fn should_error_on_invalid_arg_type() { fn foo(_: i32) {} - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new().push_owned(123_u32); let result = func.call(args); assert_eq!( @@ -240,7 +329,7 @@ mod tests { fn should_error_on_invalid_arg_ownership() { fn foo(_: &i32) {} - let mut func = foo.into_function(); + let func = foo.into_function(); let args = ArgList::new().push_owned(123_i32); let result = func.call(args); assert_eq!( @@ -255,7 +344,7 @@ mod tests { #[test] fn should_convert_dynamic_function_with_into_function() { - fn make_function<'a, F: IntoFunction<'a, M>, M>(f: F) -> DynamicFunction<'a> { + fn make_function, M>(f: F) -> DynamicFunction { f.into_function() } diff --git a/crates/bevy_reflect/src/func/reflect_fn.rs b/crates/bevy_reflect/src/func/reflect_fn.rs new file mode 100644 index 0000000000000..d84a5ddb6864d --- /dev/null +++ b/crates/bevy_reflect/src/func/reflect_fn.rs @@ -0,0 +1,223 @@ +use bevy_utils::all_tuples; + +use crate::func::args::FromArg; +use crate::func::macros::count_tts; +use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionResult, IntoReturn, ReflectFnMut}; +use crate::Reflect; + +/// A reflection-based version of the [`Fn`] trait. +/// +/// This allows functions to be called dynamically through [reflection]. +/// +/// # Blanket Implementation +/// +/// This trait has a blanket implementation that covers: +/// - Functions and methods defined with the `fn` keyword +/// - Closures that do not capture their environment +/// - Closures that capture immutable references to their environment +/// - Closures that take ownership of captured variables +/// +/// For each of the above cases, the function signature may only have up to 15 arguments, +/// not including an optional receiver argument (often `&self` or `&mut self`). +/// This optional receiver argument may be either a mutable or immutable reference to a type. +/// If the return type is also a reference, its lifetime will be bound to the lifetime of this receiver. +/// +/// See the [module-level documentation] for more information on valid signatures. +/// +/// To handle closures that capture mutable references to their environment, +/// see the [`ReflectFnMut`] trait instead. +/// +/// Arguments are expected to implement [`FromArg`], and the return type is expected to implement [`IntoReturn`]. +/// Both of these traits are automatically implemented when using the `Reflect` [derive macro]. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{ArgList, FunctionInfo, ReflectFn, TypedFunction}; +/// # +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); +/// let info = add.get_function_info(); +/// +/// let value = add.reflect_call(args, &info).unwrap().unwrap_owned(); +/// assert_eq!(value.take::().unwrap(), 100); +/// ``` +/// +/// # Trait Parameters +/// +/// This trait has a `Marker` type parameter that is used to get around issues with +/// [unconstrained type parameters] when defining impls with generic arguments or return types. +/// This `Marker` can be any type, provided it doesn't conflict with other implementations. +/// +/// Additionally, it has a lifetime parameter, `'env`, that is used to bound the lifetime of the function. +/// For most functions, this will end up just being `'static`, +/// however, closures that borrow from their environment will have a lifetime bound to that environment. +/// +/// [reflection]: crate +/// [module-level documentation]: crate::func +/// [derive macro]: derive@crate::Reflect +/// [unconstrained type parameters]: https://doc.rust-lang.org/error_codes/E0207.html +pub trait ReflectFn<'env, Marker>: ReflectFnMut<'env, Marker> { + /// Call the function with the given arguments and return the result. + fn reflect_call<'a>(&self, args: ArgList<'a>, info: &FunctionInfo) -> FunctionResult<'a>; +} + +/// Helper macro for implementing [`ReflectFn`] on Rust closures. +/// +/// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): +/// - `fn(arg0, arg1, ..., argN) -> R` +/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +macro_rules! impl_reflect_fn { + ($(($Arg:ident, $arg:ident)),*) => { + // === (...) -> ReturnType === // + impl<'env, $($Arg,)* ReturnType, Function> ReflectFn<'env, fn($($Arg),*) -> [ReturnType]> for Function + where + $($Arg: FromArg,)* + // This clause allows us to convert `ReturnType` into `Return` + ReturnType: IntoReturn + Reflect, + Function: Fn($($Arg),*) -> ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> Fn($($Arg::Item<'a>),*) -> ReturnType + 'env, + { + fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!($($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [$($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + #[allow(unused_mut)] + let mut _index = 0; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)($($arg,)*).into_return()) + } + } + + // === (&self, ...) -> &ReturnType === // + impl<'env, Receiver, $($Arg,)* ReturnType, Function> ReflectFn<'env, fn(&Receiver, $($Arg),*) -> &ReturnType> for Function + where + Receiver: Reflect, + $($Arg: FromArg,)* + ReturnType: Reflect, + // This clause allows us to convert `&ReturnType` into `Return` + for<'a> &'a ReturnType: IntoReturn, + Function: for<'a> Fn(&'a Receiver, $($Arg),*) -> &'a ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> Fn(&'a Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, + { + fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_ref::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)(receiver, $($arg,)*).into_return()) + } + } + + // === (&mut self, ...) -> &mut ReturnType === // + impl<'env, Receiver, $($Arg,)* ReturnType, Function> ReflectFn<'env, fn(&mut Receiver, $($Arg),*) -> &mut ReturnType> for Function + where + Receiver: Reflect, + $($Arg: FromArg,)* + ReturnType: Reflect, + // This clause allows us to convert `&mut ReturnType` into `Return` + for<'a> &'a mut ReturnType: IntoReturn, + Function: for<'a> Fn(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> Fn(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut ReturnType + 'env, + { + fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)(receiver, $($arg,)*).into_return()) + } + } + + // === (&mut self, ...) -> &ReturnType === // + impl<'env, Receiver, $($Arg,)* ReturnType, Function> ReflectFn<'env, fn(&mut Receiver, $($Arg),*) -> &ReturnType> for Function + where + Receiver: Reflect, + $($Arg: FromArg,)* + ReturnType: Reflect, + // This clause allows us to convert `&ReturnType` into `Return` + for<'a> &'a ReturnType: IntoReturn, + Function: for<'a> Fn(&'a mut Receiver, $($Arg),*) -> &'a ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> Fn(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, + { + fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)(receiver, $($arg,)*).into_return()) + } + } + }; +} + +all_tuples!(impl_reflect_fn, 0, 15, Arg, arg); diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs new file mode 100644 index 0000000000000..91316e2462a1d --- /dev/null +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -0,0 +1,233 @@ +use bevy_utils::all_tuples; + +use crate::func::args::FromArg; +use crate::func::macros::count_tts; +use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionResult, IntoReturn}; +use crate::Reflect; + +/// A reflection-based version of the [`FnMut`] trait. +/// +/// This allows functions to be called dynamically through [reflection]. +/// +/// It is a supertrait of [`ReflectFn`], and is used for closures that may mutate their environment. +/// +/// # Blanket Implementation +/// +/// This trait has a blanket implementation that covers everything that [`ReflectFn`] does: +/// - Functions and methods defined with the `fn` keyword +/// - Closures that do not capture their environment +/// - Closures that capture immutable references to their environment +/// - Closures that take ownership of captured variables +/// +/// But also allows for: +/// - Closures that capture mutable references to their environment +/// +/// For each of the above cases, the function signature may only have up to 15 arguments, +/// not including an optional receiver argument (often `&self` or `&mut self`). +/// This optional receiver argument may be either a mutable or immutable reference to a type. +/// If the return type is also a reference, its lifetime will be bound to the lifetime of this receiver. +/// +/// See the [module-level documentation] for more information on valid signatures. +/// +/// Arguments are expected to implement [`FromArg`], and the return type is expected to implement [`IntoReturn`]. +/// Both of these traits are automatically implemented when using the `Reflect` [derive macro]. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{ArgList, FunctionInfo, ReflectFnMut, TypedFunction}; +/// # +/// let mut list: Vec = vec![1, 3]; +/// +/// // `insert` is a closure that captures a mutable reference to `list` +/// let mut insert = |index: usize, value: i32| { +/// list.insert(index, value); +/// }; +/// +/// let args = ArgList::new().push_owned(1_usize).push_owned(2_i32); +/// let info = insert.get_function_info(); +/// +/// insert.reflect_call_mut(args, &info).unwrap(); +/// assert_eq!(list, vec![1, 2, 3]); +/// ``` +/// +/// # Trait Parameters +/// +/// This trait has a `Marker` type parameter that is used to get around issues with +/// [unconstrained type parameters] when defining impls with generic arguments or return types. +/// This `Marker` can be any type, provided it doesn't conflict with other implementations. +/// +/// Additionally, it has a lifetime parameter, `'env`, that is used to bound the lifetime of the function. +/// For most functions, this will end up just being `'static`, +/// however, closures that borrow from their environment will have a lifetime bound to that environment. +/// +/// [reflection]: crate +/// [`ReflectFn`]: crate::func::ReflectFn +/// [module-level documentation]: crate::func +/// [derive macro]: derive@crate::Reflect +/// [unconstrained type parameters]: https://doc.rust-lang.org/error_codes/E0207.html +pub trait ReflectFnMut<'env, Marker> { + /// Call the function with the given arguments and return the result. + fn reflect_call_mut<'a>( + &mut self, + args: ArgList<'a>, + info: &FunctionInfo, + ) -> FunctionResult<'a>; +} + +/// Helper macro for implementing [`ReflectFnMut`] on Rust closures. +/// +/// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): +/// - `fn(arg0, arg1, ..., argN) -> R` +/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +macro_rules! impl_reflect_fn_mut { + ($(($Arg:ident, $arg:ident)),*) => { + // === (...) -> ReturnType === // + impl<'env, $($Arg,)* ReturnType, Function> ReflectFnMut<'env, fn($($Arg),*) -> [ReturnType]> for Function + where + $($Arg: FromArg,)* + // This clause allows us to convert `ReturnType` into `Return` + ReturnType: IntoReturn + Reflect, + Function: FnMut($($Arg),*) -> ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> FnMut($($Arg::Item<'a>),*) -> ReturnType + 'env, + { + fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!($($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [$($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + #[allow(unused_mut)] + let mut _index = 0; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)($($arg,)*).into_return()) + } + } + + // === (&self, ...) -> &ReturnType === // + impl<'env, Receiver, $($Arg,)* ReturnType, Function> ReflectFnMut<'env, fn(&Receiver, $($Arg),*) -> &ReturnType> for Function + where + Receiver: Reflect, + $($Arg: FromArg,)* + ReturnType: Reflect, + // This clause allows us to convert `&ReturnType` into `Return` + for<'a> &'a ReturnType: IntoReturn, + Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> FnMut(&'a Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, + { + fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_ref::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)(receiver, $($arg,)*).into_return()) + } + } + + // === (&mut self, ...) -> &mut ReturnType === // + impl<'env, Receiver, $($Arg,)* ReturnType, Function> ReflectFnMut<'env, fn(&mut Receiver, $($Arg),*) -> &mut ReturnType> for Function + where + Receiver: Reflect, + $($Arg: FromArg,)* + ReturnType: Reflect, + // This clause allows us to convert `&mut ReturnType` into `Return` + for<'a> &'a mut ReturnType: IntoReturn, + Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut ReturnType + 'env, + { + fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)(receiver, $($arg,)*).into_return()) + } + } + + // === (&mut self, ...) -> &ReturnType === // + impl<'env, Receiver, $($Arg,)* ReturnType, Function> ReflectFnMut<'env, fn(&mut Receiver, $($Arg),*) -> &ReturnType> for Function + where + Receiver: Reflect, + $($Arg: FromArg,)* + ReturnType: Reflect, + // This clause allows us to convert `&ReturnType` into `Return` + for<'a> &'a ReturnType: IntoReturn, + Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType + 'env, + // This clause essentially asserts that `Arg::Item` is the same type as `Arg` + Function: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, + { + fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + if args.len() != COUNT { + return Err(FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + + Ok((self)(receiver, $($arg,)*).into_return()) + } + } + }; +} + +all_tuples!(impl_reflect_fn_mut, 0, 15, Arg, arg); From 46257607b80712bf71ffa08ccf86671bac149eee Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 4 Jul 2024 10:30:21 -0700 Subject: [PATCH 03/10] Fix docs and move FunctionResult --- crates/bevy_reflect/src/func/args/arg.rs | 3 ++- crates/bevy_reflect/src/func/args/info.rs | 13 +++++++++---- crates/bevy_reflect/src/func/args/list.rs | 3 ++- crates/bevy_reflect/src/func/args/mod.rs | 3 ++- crates/bevy_reflect/src/func/error.rs | 13 ++++++++++++- crates/bevy_reflect/src/func/function.rs | 10 +--------- crates/bevy_reflect/src/func/info.rs | 13 ++++++++++--- crates/bevy_reflect/src/func/return_type.rs | 3 ++- 8 files changed, 40 insertions(+), 21 deletions(-) diff --git a/crates/bevy_reflect/src/func/args/arg.rs b/crates/bevy_reflect/src/func/args/arg.rs index a79ab592aab09..bc523af2851a2 100644 --- a/crates/bevy_reflect/src/func/args/arg.rs +++ b/crates/bevy_reflect/src/func/args/arg.rs @@ -1,9 +1,10 @@ use crate::func::args::{ArgError, ArgInfo, Ownership}; use crate::Reflect; -/// Represents an argument that can be passed to a [`DynamicFunction`]. +/// Represents an argument that can be passed to a [`DynamicFunction`] or [`DynamicClosure`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug)] pub enum Arg<'a> { Owned(Box), diff --git a/crates/bevy_reflect/src/func/args/info.rs b/crates/bevy_reflect/src/func/args/info.rs index baf85e0207918..21f8a7099c223 100644 --- a/crates/bevy_reflect/src/func/args/info.rs +++ b/crates/bevy_reflect/src/func/args/info.rs @@ -3,10 +3,11 @@ use alloc::borrow::Cow; use crate::func::args::{GetOwnership, Ownership}; use crate::TypePath; -/// Type information for an [`Arg`] used in a [`DynamicFunction`]. +/// Type information for an [`Arg`] used in a [`DynamicFunction`] or [`DynamicClosure`]. /// -/// [`Arg`]: crate::func::Arg -/// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`Arg`]: crate::func::args::Arg +/// [`DynamicFunction`]: crate::func::function::DynamicFunction +/// [`DynamicClosure`]: crate::func::closure::DynamicClosure #[derive(Debug, Clone)] pub struct ArgInfo { /// The index of the argument within its function. @@ -54,10 +55,14 @@ impl ArgInfo { /// This is because the name needs to be manually set using [`Self::with_name`] /// since the name can't be inferred from the function type alone. /// - /// For [`DynamicFunctions`] created using [`IntoFunction`], the name will always be `None`. + /// For [`DynamicFunctions`] created using [`IntoFunction`] + /// or [`DynamicClosures`] created using [`IntoClosure`], + /// the name will always be `None`. /// /// [`DynamicFunctions`]: crate::func::DynamicFunction /// [`IntoFunction`]: crate::func::IntoFunction + /// [`DynamicClosures`]: crate::func::DynamicClosure + /// [`IntoClosure`]: crate::func::IntoClosure pub fn name(&self) -> Option<&str> { self.name.as_deref() } diff --git a/crates/bevy_reflect/src/func/args/list.rs b/crates/bevy_reflect/src/func/args/list.rs index 984662f3f5c17..319d6f88537eb 100644 --- a/crates/bevy_reflect/src/func/args/list.rs +++ b/crates/bevy_reflect/src/func/args/list.rs @@ -1,7 +1,7 @@ use crate::func::args::Arg; use crate::Reflect; -/// A list of arguments that can be passed to a [`DynamicFunction`]. +/// A list of arguments that can be passed to a [`DynamicFunction`] or [`DynamicClosure`]. /// /// # Example /// @@ -24,6 +24,7 @@ use crate::Reflect; /// ``` /// /// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Default, Debug)] pub struct ArgList<'a>(Vec>); diff --git a/crates/bevy_reflect/src/func/args/mod.rs b/crates/bevy_reflect/src/func/args/mod.rs index adcbc0ec641d0..57237b70e8207 100644 --- a/crates/bevy_reflect/src/func/args/mod.rs +++ b/crates/bevy_reflect/src/func/args/mod.rs @@ -1,6 +1,7 @@ -//! Argument types and utilities for working with [`DynamicFunctions`]. +//! Argument types and utilities for working with [`DynamicFunctions`] and [`DynamicClosures`]. //! //! [`DynamicFunctions`]: crate::func::DynamicFunction +//! [`DynamicClosures`]: crate::func::DynamicClosure pub use arg::*; pub use error::*; diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index 65290b66d4b59..81d9f535d4deb 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -1,9 +1,11 @@ use crate::func::args::ArgError; +use crate::func::Return; use thiserror::Error; -/// An error that occurs when calling a [`DynamicFunction`]. +/// An error that occurs when calling a [`DynamicFunction`] or [`DynamicClosure`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug, Error, PartialEq)] pub enum FunctionError { /// An error occurred while converting an argument. @@ -13,3 +15,12 @@ pub enum FunctionError { #[error("expected {expected} arguments but received {received}")] InvalidArgCount { expected: usize, received: usize }, } + +/// The result of calling a dynamic [`DynamicFunction`] or [`DynamicClosure`]. +/// +/// Returns `Ok(value)` if the function was called successfully, +/// where `value` is the [`Return`] value of the function. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure +pub type FunctionResult<'a> = Result, FunctionError>; diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index 38b60cfb8eb2d..63e5b0b3ee343 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -3,16 +3,8 @@ use core::fmt::{Debug, Formatter}; use std::sync::Arc; use crate::func::args::{ArgInfo, ArgList}; -use crate::func::error::FunctionError; use crate::func::info::FunctionInfo; -use crate::func::return_type::Return; -use crate::func::{IntoFunction, ReturnInfo}; - -/// The result of calling a dynamic [`DynamicFunction`]. -/// -/// Returns `Ok(value)` if the function was called successfully, -/// where `value` is the [`Return`] value of the function. -pub type FunctionResult<'a> = Result, FunctionError>; +use crate::func::{FunctionResult, IntoFunction, ReturnInfo}; /// A dynamic representation of a Rust function. /// diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 8e0bccd69df77..789b768219bf6 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -5,9 +5,13 @@ use bevy_utils::all_tuples; use crate::func::args::{ArgInfo, GetOwnership, Ownership}; use crate::TypePath; -/// Type information for a [`DynamicFunction`]. +/// Type information for a [`DynamicFunction`] or [`DynamicClosure`]. +/// +/// This information can be retrieved from certain functions and closures +/// using the [`TypedFunction`] trait. /// /// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug, Clone)] pub struct FunctionInfo { name: Option>, @@ -63,11 +67,13 @@ impl FunctionInfo { /// The name of the function, if it was given one. /// - /// For [`DynamicFunctions`] created using [`IntoFunction`], + /// For [`DynamicFunctions`] created using [`IntoFunction`] or [`DynamicClosures`] created using [`IntoClosure`], /// the name will always be the full path to the function as returned by [`std::any::type_name`]. /// /// [`DynamicFunctions`]: crate::func::DynamicFunction /// [`IntoFunction`]: crate::func::IntoFunction + /// [`DynamicClosures`]: crate::func::DynamicClosure + /// [`IntoClosure`]: crate::func::IntoClosure pub fn name(&self) -> Option<&str> { self.name.as_deref() } @@ -94,9 +100,10 @@ impl Default for FunctionInfo { } } -/// Information about the return type of a [`DynamicFunction`]. +/// Information about the return type of a [`DynamicFunction`] or [`DynamicClosure`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug, Clone)] pub struct ReturnInfo { type_path: &'static str, diff --git a/crates/bevy_reflect/src/func/return_type.rs b/crates/bevy_reflect/src/func/return_type.rs index 3e426350ef18b..59a0f36adc3ef 100644 --- a/crates/bevy_reflect/src/func/return_type.rs +++ b/crates/bevy_reflect/src/func/return_type.rs @@ -1,8 +1,9 @@ use crate::Reflect; -/// The return type of a [`DynamicFunction`]. +/// The return type of a [`DynamicFunction`] or [`DynamicClosure`]. /// /// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicClosure`]: crate::func::DynamicClosure #[derive(Debug)] pub enum Return<'a> { /// The function returns nothing (i.e. it returns `()`). From 228f10be2aa0194e05ba5c8f17e159f3b0519b5a Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 4 Jul 2024 10:44:15 -0700 Subject: [PATCH 04/10] Update example --- examples/reflection/function_reflection.rs | 41 ++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/examples/reflection/function_reflection.rs b/examples/reflection/function_reflection.rs index 8f02cec4574f4..d9e66add4573b 100644 --- a/examples/reflection/function_reflection.rs +++ b/examples/reflection/function_reflection.rs @@ -8,7 +8,8 @@ use bevy::reflect::func::args::ArgInfo; use bevy::reflect::func::{ - ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo, + ArgList, DynamicClosure, DynamicFunction, FunctionInfo, IntoClosure, IntoFunction, Return, + ReturnInfo, }; use bevy::reflect::Reflect; @@ -37,7 +38,7 @@ fn main() { // Luckily, Bevy's reflection crate comes with a set of tools for doing just that! // We do this by first converting our function into the reflection-based `DynamicFunction` type // using the `IntoFunction` trait. - let mut function: DynamicFunction = dbg!(add.into_function()); + let function: DynamicFunction = dbg!(add.into_function()); // This time, you'll notice that `DynamicFunction` doesn't take any information about the function's arguments or return value. // This is because `DynamicFunction` checks the types of the arguments and return value at runtime. @@ -55,22 +56,34 @@ fn main() { let value: Box = return_value.unwrap_owned(); assert_eq!(value.take::().unwrap(), 4); - // The same can also be done for closures. + // The same can also be done for closures that capture references to their environment + // using the `IntoClosure` trait. let mut count = 0; - let increment = |amount: i32| { - count += amount; - }; - let increment_function: DynamicFunction = dbg!(increment.into_function()); + let increment = |amount: i32| count += amount; + + let closure: DynamicClosure = dbg!(increment.into_closure()); let args = dbg!(ArgList::new().push_owned(5_i32)); - // `DynamicFunction`s containing closures that capture their environment like this one + // `DynamicClosure`s that mutably capture their environment like this one // may need to be dropped before those captured variables may be used again. - // This can be done manually with `drop` or by using the `Function::call_once` method. - dbg!(increment_function.call_once(args).unwrap()); + // This can be done manually with `drop(closure)` or by using the `DynamicClosure::call_once` method. + dbg!(closure.call_once(args).unwrap()); assert_eq!(count, 5); + // Note that `DynamicFunction` just requires a `'static` lifetime. + // This means that closures that capture variables by taking ownership of them + // can still be converted to a `DynamicFunction`. + let minimum = 5; + let clamp = move |value: i32| value.max(minimum); + + let function: DynamicFunction = dbg!(clamp.into_function()); + let args = dbg!(ArgList::new().push_owned(2_i32)); + let return_value = dbg!(function.call(args).unwrap()); + let value: Box = return_value.unwrap_owned(); + assert_eq!(value.take::().unwrap(), 5); + // As stated before, this works for many kinds of simple functions. // Functions with non-reflectable arguments or return values may not be able to be converted. - // Generic functions are also not supported. + // Generic functions are also not supported (unless manually monomorphized like `foo::.into_function()`). // Additionally, the lifetime of the return value is tied to the lifetime of the first argument. // However, this means that many methods (i.e. functions with a `self` parameter) are also supported: #[derive(Reflect, Default)] @@ -92,12 +105,12 @@ fn main() { let mut data = Data::default(); - let mut set_value = dbg!(Data::set_value.into_function()); + let set_value = dbg!(Data::set_value.into_function()); let args = dbg!(ArgList::new().push_mut(&mut data)).push_owned(String::from("Hello, world!")); dbg!(set_value.call(args).unwrap()); assert_eq!(data.value, "Hello, world!"); - let mut get_value = dbg!(Data::get_value.into_function()); + let get_value = dbg!(Data::get_value.into_function()); let args = dbg!(ArgList::new().push_ref(&data)); let return_value = dbg!(get_value.call(args).unwrap()); let value: &dyn Reflect = return_value.unwrap_ref(); @@ -115,7 +128,7 @@ fn main() { container.as_ref().unwrap() } - let mut get_or_insert_function = dbg!(DynamicFunction::new( + let get_or_insert_function = dbg!(DynamicFunction::new( |mut args, info| { let container_info = &info.args()[1]; let value_info = &info.args()[0]; From 015663d9a85fb0ef1c525b965dd86fa3bd6b8dbf Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 5 Jul 2024 13:04:05 -0700 Subject: [PATCH 05/10] Fix compile tests --- .../compile_fail/tests/into_function/arguments_fail.rs | 4 ++-- .../compile_fail/tests/into_function/return_fail.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs b/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs index 27d7e89c57152..9187e874ec823 100644 --- a/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs @@ -33,8 +33,8 @@ fn main() { let _ = pass.into_function(); let _ = too_many_arguments.into_function(); - //~^ ERROR: no method named `into_function` found + //~^ E0599 let _ = argument_not_reflect.into_function(); - //~^ ERROR: no method named `into_function` found + //~^ E0599 } diff --git a/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs b/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs index a98322be91b18..d73a3406b306b 100644 --- a/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs @@ -25,10 +25,10 @@ fn main() { let _ = pass.into_function(); let _ = return_not_reflect.into_function(); - //~^ ERROR: no method named `into_function` found + //~^ E0599 let _ = return_with_lifetime_pass.into_function(); let _ = return_with_invalid_lifetime.into_function(); - //~^ ERROR: no method named `into_function` found + //~^ E0599 } From 31b6176b70eba444d04ebfebbb44a4e154f4d099 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 5 Jul 2024 13:18:35 -0700 Subject: [PATCH 06/10] Add compile fail test --- .../tests/into_function/closure_fail.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs diff --git a/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs b/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs new file mode 100644 index 0000000000000..901a5a3923f67 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs @@ -0,0 +1,20 @@ +//@no-rustfix +#![allow(unused)] + +use bevy_reflect::func::IntoFunction; +use bevy_reflect::Reflect; + +fn main() { + let value = String::from("Hello, World!"); + let closure_capture_owned = move || println!("{}", value); + + // Should pass: + let _ = closure_capture_owned.into_function(); + + let value = String::from("Hello, World!"); + let closure_capture_reference = || println!("{}", &value); + //~^ E0373 + + // Above error due to this line: + let _ = closure_capture_reference.into_function(); +} From f6a66e14341925ca4099399927c43499c535fd8b Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sun, 14 Jul 2024 17:25:05 -0700 Subject: [PATCH 07/10] Restructure closure-related modules --- crates/bevy_reflect/src/func/args/info.rs | 2 +- crates/bevy_reflect/src/func/{ => closures}/closure.rs | 3 +-- crates/bevy_reflect/src/func/{ => closures}/into_closure.rs | 3 +-- crates/bevy_reflect/src/func/closures/mod.rs | 5 +++++ crates/bevy_reflect/src/func/mod.rs | 6 ++---- 5 files changed, 10 insertions(+), 9 deletions(-) rename crates/bevy_reflect/src/func/{ => closures}/closure.rs (98%) rename crates/bevy_reflect/src/func/{ => closures}/into_closure.rs (91%) create mode 100644 crates/bevy_reflect/src/func/closures/mod.rs diff --git a/crates/bevy_reflect/src/func/args/info.rs b/crates/bevy_reflect/src/func/args/info.rs index 21f8a7099c223..34f10cda533a0 100644 --- a/crates/bevy_reflect/src/func/args/info.rs +++ b/crates/bevy_reflect/src/func/args/info.rs @@ -7,7 +7,7 @@ use crate::TypePath; /// /// [`Arg`]: crate::func::args::Arg /// [`DynamicFunction`]: crate::func::function::DynamicFunction -/// [`DynamicClosure`]: crate::func::closure::DynamicClosure +/// [`DynamicClosure`]: crate::func::closures::DynamicClosure #[derive(Debug, Clone)] pub struct ArgInfo { /// The index of the argument within its function. diff --git a/crates/bevy_reflect/src/func/closure.rs b/crates/bevy_reflect/src/func/closures/closure.rs similarity index 98% rename from crates/bevy_reflect/src/func/closure.rs rename to crates/bevy_reflect/src/func/closures/closure.rs index 24bf10bb5cb09..f64b75941eb9f 100644 --- a/crates/bevy_reflect/src/func/closure.rs +++ b/crates/bevy_reflect/src/func/closures/closure.rs @@ -3,8 +3,7 @@ use core::fmt::{Debug, Formatter}; use crate::func::args::{ArgInfo, ArgList}; use crate::func::info::FunctionInfo; -use crate::func::into_closure::IntoClosure; -use crate::func::{FunctionResult, ReturnInfo}; +use crate::func::{FunctionResult, IntoClosure, ReturnInfo}; /// A dynamic representation of a Rust closure. /// diff --git a/crates/bevy_reflect/src/func/into_closure.rs b/crates/bevy_reflect/src/func/closures/into_closure.rs similarity index 91% rename from crates/bevy_reflect/src/func/into_closure.rs rename to crates/bevy_reflect/src/func/closures/into_closure.rs index 439c24b0746f4..0baa9061e8879 100644 --- a/crates/bevy_reflect/src/func/into_closure.rs +++ b/crates/bevy_reflect/src/func/closures/into_closure.rs @@ -1,5 +1,4 @@ -use crate::func::closure::DynamicClosure; -use crate::func::{ReflectFnMut, TypedFunction}; +use crate::func::{DynamicClosure, ReflectFnMut, TypedFunction}; /// A trait for types that can be converted into a [`DynamicClosure`]. /// diff --git a/crates/bevy_reflect/src/func/closures/mod.rs b/crates/bevy_reflect/src/func/closures/mod.rs new file mode 100644 index 0000000000000..c2c9a861fbc9d --- /dev/null +++ b/crates/bevy_reflect/src/func/closures/mod.rs @@ -0,0 +1,5 @@ +pub use closure::*; +pub use into_closure::*; + +mod closure; +mod into_closure; diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index f7a966095dbc9..c46873bd519b7 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -87,11 +87,10 @@ //! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ //! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check -pub use closure::*; +pub use closures::*; pub use error::*; pub use function::*; pub use info::*; -pub use into_closure::*; pub use into_function::*; pub use reflect_fn::*; pub use reflect_fn_mut::*; @@ -100,11 +99,10 @@ pub use return_type::*; pub use args::{Arg, ArgError, ArgList}; pub mod args; -mod closure; +mod closures; mod error; mod function; mod info; -mod into_closure; mod into_function; pub(crate) mod macros; mod reflect_fn; From e6c1c6dc270d9539cfba08c753614d636053cc6b Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sun, 14 Jul 2024 23:42:20 -0700 Subject: [PATCH 08/10] Split DynamicClosure into DynamicClosure/DynamicClosureMut DynamicFunction is now further restricted to standard function types (well, almost, Rust doesn't like impls on `fn` types) --- .../tests/into_function/closure_fail.rs | 9 +- .../{closure.rs => dynamic_closure.rs} | 88 ++++--- .../src/func/closures/dynamic_closure_mut.rs | 222 +++++++++++++++++ .../src/func/closures/into_closure.rs | 51 +++- .../src/func/closures/into_closure_mut.rs | 76 ++++++ crates/bevy_reflect/src/func/closures/mod.rs | 8 +- crates/bevy_reflect/src/func/function.rs | 23 ++ crates/bevy_reflect/src/func/into_function.rs | 156 +++++++++++- crates/bevy_reflect/src/func/mod.rs | 225 +++--------------- .../bevy_reflect/src/func/reflect_fn_mut.rs | 2 +- examples/reflection/function_reflection.rs | 38 +-- 11 files changed, 624 insertions(+), 274 deletions(-) rename crates/bevy_reflect/src/func/closures/{closure.rs => dynamic_closure.rs} (68%) create mode 100644 crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs create mode 100644 crates/bevy_reflect/src/func/closures/into_closure_mut.rs diff --git a/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs b/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs index 901a5a3923f67..15316677ca5e0 100644 --- a/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/into_function/closure_fail.rs @@ -1,4 +1,3 @@ -//@no-rustfix #![allow(unused)] use bevy_reflect::func::IntoFunction; @@ -8,13 +7,13 @@ fn main() { let value = String::from("Hello, World!"); let closure_capture_owned = move || println!("{}", value); - // Should pass: let _ = closure_capture_owned.into_function(); + //~^ E0277 let value = String::from("Hello, World!"); - let closure_capture_reference = || println!("{}", &value); - //~^ E0373 + let closure_capture_reference = || println!("{}", value); - // Above error due to this line: let _ = closure_capture_reference.into_function(); + // ↑ This should be an error (E0277) but `compile_fail_utils` fails to pick it up + // when the `closure_capture_owned` test is present } diff --git a/crates/bevy_reflect/src/func/closures/closure.rs b/crates/bevy_reflect/src/func/closures/dynamic_closure.rs similarity index 68% rename from crates/bevy_reflect/src/func/closures/closure.rs rename to crates/bevy_reflect/src/func/closures/dynamic_closure.rs index f64b75941eb9f..8fbdbbfd2b62f 100644 --- a/crates/bevy_reflect/src/func/closures/closure.rs +++ b/crates/bevy_reflect/src/func/closures/dynamic_closure.rs @@ -7,8 +7,9 @@ use crate::func::{FunctionResult, IntoClosure, ReturnInfo}; /// A dynamic representation of a Rust closure. /// -/// For our purposes, a "closure" is just a callable that may reference its environment. -/// This includes any type of Rust function or closure. +/// This type can be used to represent any Rust closure that captures its environment immutably. +/// For closures that need to capture their environment mutably, +/// see [`DynamicClosureMut`]. /// /// This type can be seen as a superset of [`DynamicFunction`]. /// @@ -24,35 +25,29 @@ use crate::func::{FunctionResult, IntoClosure, ReturnInfo}; /// ``` /// # use bevy_reflect::func::{ArgList, DynamicClosure, FunctionInfo, IntoClosure}; /// # -/// let mut list: Vec = vec![1, 2, 3]; +/// let punct = String::from("!!!"); /// -/// // `replace` is a closure that captures a mutable reference to `list` -/// let mut replace = |index: usize, value: i32| -> i32 { -/// let old_value = list[index]; -/// list[index] = value; -/// old_value +/// let punctuate = |text: &String| -> String { +/// format!("{}{}", text, punct) /// }; /// /// // Convert the closure into a dynamic closure using `IntoClosure::into_closure` -/// let mut func: DynamicClosure = replace.into_closure(); +/// let mut func: DynamicClosure = punctuate.into_closure(); /// /// // Dynamically call the closure: -/// let args = ArgList::default().push_owned(1_usize).push_owned(-2_i32); +/// let text = String::from("Hello, world"); +/// let args = ArgList::default().push_ref(&text); /// let value = func.call(args).unwrap().unwrap_owned(); /// /// // Check the result: -/// assert_eq!(value.take::().unwrap(), 2); -/// -/// // Note that `func` still has a reference to `list`, -/// // so we need to drop it before we can access `list` again. -/// drop(func); -/// assert_eq!(list, vec![1, -2, 3]); +/// assert_eq!(value.take::().unwrap(), "Hello, world!!!"); /// ``` /// +/// [`DynamicClosureMut`]: crate::func::closures::DynamicClosureMut /// [`DynamicFunction`]: crate::func::DynamicFunction pub struct DynamicClosure<'env> { info: FunctionInfo, - func: Box FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>, + func: Box Fn(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>, } impl<'env> DynamicClosure<'env> { @@ -62,7 +57,7 @@ impl<'env> DynamicClosure<'env> { /// /// It's important that the closure signature matches the provided [`FunctionInfo`]. /// This info is used to validate the arguments and return value. - pub fn new FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>( + pub fn new Fn(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>( func: F, info: FunctionInfo, ) -> Self { @@ -107,41 +102,17 @@ impl<'env> DynamicClosure<'env> { /// /// ``` /// # use bevy_reflect::func::{IntoClosure, ArgList}; + /// let c = 23; /// let add = |a: i32, b: i32| -> i32 { - /// a + b + /// a + b + c /// }; /// /// let mut func = add.into_closure().with_name("add"); /// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); /// let result = func.call(args).unwrap().unwrap_owned(); - /// assert_eq!(result.take::().unwrap(), 100); + /// assert_eq!(result.take::().unwrap(), 123); /// ``` - pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { - (self.func)(args, &self.info) - } - - /// Call the closure with the given arguments and consume the closure. - /// - /// This is useful for closures that capture their environment because otherwise - /// any captured variables would still be borrowed by this closure. - /// - /// # Example - /// - /// ``` - /// # use bevy_reflect::func::{IntoClosure, ArgList}; - /// let mut count = 0; - /// let increment = |amount: i32| count += amount; - /// - /// let increment_function = increment.into_closure(); - /// let args = ArgList::new().push_owned(5_i32); - /// - /// // We need to drop `increment_function` here so that we - /// // can regain access to `count`. - /// // `call_once` does this automatically for us. - /// increment_function.call_once(args).unwrap(); - /// assert_eq!(count, 5); - /// ``` - pub fn call_once(mut self, args: ArgList) -> FunctionResult { + pub fn call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { (self.func)(args, &self.info) } @@ -182,3 +153,28 @@ impl<'env> IntoClosure<'env, ()> for DynamicClosure<'env> { self } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_overwrite_closure_name() { + let c = 23; + let func = (|a: i32, b: i32| a + b + c) + .into_closure() + .with_name("my_closure"); + assert_eq!(func.info().name(), Some("my_closure")); + } + + #[test] + fn should_convert_dynamic_closure_with_into_closure() { + fn make_closure<'env, F: IntoClosure<'env, M>, M>(f: F) -> DynamicClosure<'env> { + f.into_closure() + } + + let c = 23; + let closure: DynamicClosure = make_closure(|a: i32, b: i32| a + b + c); + let _: DynamicClosure = make_closure(closure); + } +} diff --git a/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs b/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs new file mode 100644 index 0000000000000..eb63463d9a87d --- /dev/null +++ b/crates/bevy_reflect/src/func/closures/dynamic_closure_mut.rs @@ -0,0 +1,222 @@ +use alloc::borrow::Cow; +use core::fmt::{Debug, Formatter}; + +use crate::func::args::{ArgInfo, ArgList}; +use crate::func::info::FunctionInfo; +use crate::func::{FunctionResult, IntoClosureMut, ReturnInfo}; + +/// A dynamic representation of a Rust closure. +/// +/// This type can be used to represent any Rust closure that captures its environment mutably. +/// For closures that only need to capture their environment immutably, +/// consider using [`DynamicClosure`]. +/// +/// This type can be seen as a superset of [`DynamicClosure`]. +/// +/// See the [module-level documentation] for more information. +/// +/// You will generally not need to construct this manually. +/// Instead, many functions and closures can be automatically converted using the [`IntoClosureMut`] trait. +/// +/// # Example +/// +/// Most of the time, a [`DynamicClosureMut`] can be created using the [`IntoClosureMut`] trait: +/// +/// ``` +/// # use bevy_reflect::func::{ArgList, DynamicClosureMut, FunctionInfo, IntoClosureMut}; +/// # +/// let mut list: Vec = vec![1, 2, 3]; +/// +/// // `replace` is a closure that captures a mutable reference to `list` +/// let mut replace = |index: usize, value: i32| -> i32 { +/// let old_value = list[index]; +/// list[index] = value; +/// old_value +/// }; +/// +/// // Convert the closure into a dynamic closure using `IntoClosureMut::into_closure_mut` +/// let mut func: DynamicClosureMut = replace.into_closure_mut(); +/// +/// // Dynamically call the closure: +/// let args = ArgList::default().push_owned(1_usize).push_owned(-2_i32); +/// let value = func.call(args).unwrap().unwrap_owned(); +/// +/// // Check the result: +/// assert_eq!(value.take::().unwrap(), 2); +/// +/// // Note that `func` still has a reference to `list`, +/// // so we need to drop it before we can access `list` again. +/// // Alternatively, we could have called the `func` using +/// // `DynamicClosureMut::call_once` to immediately consume the closure. +/// drop(func); +/// assert_eq!(list, vec![1, -2, 3]); +/// ``` +/// +/// [`DynamicClosure`]: crate::func::closures::DynamicClosure +/// [`DynamicFunction`]: crate::func::DynamicFunction +pub struct DynamicClosureMut<'env> { + info: FunctionInfo, + func: Box FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>, +} + +impl<'env> DynamicClosureMut<'env> { + /// Create a new [`DynamicClosureMut`]. + /// + /// The given function can be used to call out to a regular function, closure, or method. + /// + /// It's important that the closure signature matches the provided [`FunctionInfo`]. + /// This info is used to validate the arguments and return value. + pub fn new FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>( + func: F, + info: FunctionInfo, + ) -> Self { + Self { + info, + func: Box::new(func), + } + } + + /// Set the name of the closure. + /// + /// For [`DynamicClosureMuts`] created using [`IntoClosureMut`], + /// the default name will always be the full path to the closure as returned by [`std::any::type_name`]. + /// + /// This default name generally does not contain the actual name of the closure, only its module path. + /// It is therefore recommended to set the name manually using this method. + /// + /// [`DynamicClosureMuts`]: DynamicClosureMut + pub fn with_name(mut self, name: impl Into>) -> Self { + self.info = self.info.with_name(name); + self + } + + /// Set the arguments of the closure. + /// + /// It is very important that the arguments match the intended closure signature, + /// as this is used to validate arguments passed to the closure. + pub fn with_args(mut self, args: Vec) -> Self { + self.info = self.info.with_args(args); + self + } + + /// Set the return information of the closure. + pub fn with_return_info(mut self, return_info: ReturnInfo) -> Self { + self.info = self.info.with_return_info(return_info); + self + } + + /// Call the closure with the given arguments. + /// + /// Variables that are captured mutably by this closure + /// won't be usable until this closure is dropped. + /// Consider using [`call_once`] if you want to consume the closure + /// immediately after calling it. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{IntoClosureMut, ArgList}; + /// let mut total = 0; + /// let add = |a: i32, b: i32| -> i32 { + /// total = a + b; + /// total + /// }; + /// + /// let mut func = add.into_closure_mut().with_name("add"); + /// let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + /// let result = func.call(args).unwrap().unwrap_owned(); + /// assert_eq!(result.take::().unwrap(), 100); + /// ``` + /// + /// [`call_once`]: DynamicClosureMut::call_once + pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { + (self.func)(args, &self.info) + } + + /// Call the closure with the given arguments and consume the closure. + /// + /// This is useful for closures that capture their environment mutably + /// because otherwise any captured variables would still be borrowed by this closure. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{IntoClosureMut, ArgList}; + /// let mut count = 0; + /// let increment = |amount: i32| count += amount; + /// + /// let increment_function = increment.into_closure_mut(); + /// let args = ArgList::new().push_owned(5_i32); + /// + /// // We need to drop `increment_function` here so that we + /// // can regain access to `count`. + /// // `call_once` does this automatically for us. + /// increment_function.call_once(args).unwrap(); + /// assert_eq!(count, 5); + /// ``` + pub fn call_once(mut self, args: ArgList) -> FunctionResult { + (self.func)(args, &self.info) + } + + /// Returns the closure info. + pub fn info(&self) -> &FunctionInfo { + &self.info + } +} + +/// Outputs the closure's signature. +/// +/// This takes the format: `DynamicClosureMut(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. +/// +/// Names for arguments and the closure itself are optional and will default to `_` if not provided. +impl<'env> Debug for DynamicClosureMut<'env> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let name = self.info.name().unwrap_or("_"); + write!(f, "DynamicClosureMut(fn {name}(")?; + + for (index, arg) in self.info.args().iter().enumerate() { + let name = arg.name().unwrap_or("_"); + let ty = arg.type_path(); + write!(f, "{name}: {ty}")?; + + if index + 1 < self.info.args().len() { + write!(f, ", ")?; + } + } + + let ret = self.info.return_info().type_path(); + write!(f, ") -> {ret})") + } +} + +impl<'env> IntoClosureMut<'env, ()> for DynamicClosureMut<'env> { + #[inline] + fn into_closure_mut(self) -> DynamicClosureMut<'env> { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_overwrite_closure_name() { + let mut total = 0; + let func = (|a: i32, b: i32| total = a + b) + .into_closure_mut() + .with_name("my_closure"); + assert_eq!(func.info().name(), Some("my_closure")); + } + + #[test] + fn should_convert_dynamic_closure_mut_with_into_closure() { + fn make_closure<'env, F: IntoClosureMut<'env, M>, M>(f: F) -> DynamicClosureMut<'env> { + f.into_closure_mut() + } + + let mut total = 0; + let closure: DynamicClosureMut = make_closure(|a: i32, b: i32| total = a + b); + let _: DynamicClosureMut = make_closure(closure); + } +} diff --git a/crates/bevy_reflect/src/func/closures/into_closure.rs b/crates/bevy_reflect/src/func/closures/into_closure.rs index 0baa9061e8879..3d191a1e1151f 100644 --- a/crates/bevy_reflect/src/func/closures/into_closure.rs +++ b/crates/bevy_reflect/src/func/closures/into_closure.rs @@ -1,16 +1,14 @@ -use crate::func::{DynamicClosure, ReflectFnMut, TypedFunction}; +use crate::func::{DynamicClosure, ReflectFn, TypedFunction}; /// A trait for types that can be converted into a [`DynamicClosure`]. /// /// This trait is automatically implemented for any type that implements -/// [`ReflectFnMut`] and [`TypedFunction`]. +/// [`ReflectFn`] and [`TypedFunction`]. /// -/// Because [`ReflectFn`] is a subtrait of [`ReflectFnMut`], -/// this trait can be seen as a supertrait of [`IntoFunction`]. +/// This trait can be seen as a supertrait of [`IntoFunction`]. /// /// See the [module-level documentation] for more information. /// -/// [`ReflectFn`]: crate::func::ReflectFn /// [`IntoFunction`]: crate::func::IntoFunction /// [module-level documentation]: crate::func pub trait IntoClosure<'env, Marker> { @@ -20,12 +18,49 @@ pub trait IntoClosure<'env, Marker> { impl<'env, F, Marker1, Marker2> IntoClosure<'env, (Marker1, Marker2)> for F where - F: ReflectFnMut<'env, Marker1> + TypedFunction + 'env, + F: ReflectFn<'env, Marker1> + TypedFunction + 'env, { - fn into_closure(mut self) -> DynamicClosure<'env> { + fn into_closure(self) -> DynamicClosure<'env> { DynamicClosure::new( - move |args, info| self.reflect_call_mut(args, info), + move |args, info| self.reflect_call(args, info), Self::function_info(), ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::func::ArgList; + + #[test] + fn should_create_dynamic_closure_from_closure() { + let c = 23; + let func = (|a: i32, b: i32| a + b + c).into_closure(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&123)); + } + + #[test] + fn should_create_dynamic_closure_from_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let func = add.into_closure(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&100)); + } + + #[test] + fn should_default_with_closure_type_name() { + let c = 23; + let func = (|a: i32, b: i32| a + b + c).into_closure(); + assert_eq!( + func.info().name(), + Some("bevy_reflect::func::closures::into_closure::tests::should_default_with_closure_type_name::{{closure}}") + ); + } +} diff --git a/crates/bevy_reflect/src/func/closures/into_closure_mut.rs b/crates/bevy_reflect/src/func/closures/into_closure_mut.rs new file mode 100644 index 0000000000000..a3daff6caadca --- /dev/null +++ b/crates/bevy_reflect/src/func/closures/into_closure_mut.rs @@ -0,0 +1,76 @@ +use crate::func::{DynamicClosureMut, ReflectFnMut, TypedFunction}; + +/// A trait for types that can be converted into a [`DynamicClosureMut`]. +/// +/// This trait is automatically implemented for any type that implements +/// [`ReflectFnMut`] and [`TypedFunction`]. +/// +/// This trait can be seen as a supertrait of [`IntoClosure`]. +/// +/// See the [module-level documentation] for more information. +/// +/// [`ReflectFn`]: crate::func::ReflectFn +/// [`IntoClosure`]: crate::func::closures::IntoClosure +/// [module-level documentation]: crate::func +pub trait IntoClosureMut<'env, Marker> { + /// Converts [`Self`] into a [`DynamicClosureMut`]. + fn into_closure_mut(self) -> DynamicClosureMut<'env>; +} + +impl<'env, F, Marker1, Marker2> IntoClosureMut<'env, (Marker1, Marker2)> for F +where + F: ReflectFnMut<'env, Marker1> + TypedFunction + 'env, +{ + fn into_closure_mut(mut self) -> DynamicClosureMut<'env> { + DynamicClosureMut::new( + move |args, info| self.reflect_call_mut(args, info), + Self::function_info(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::func::{ArgList, IntoClosure}; + + #[test] + fn should_create_dynamic_closure_mut_from_closure() { + let c = 23; + let func = (|a: i32, b: i32| a + b + c).into_closure(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&123)); + } + + #[test] + fn should_create_dynamic_closure_mut_from_closure_with_mutable_capture() { + let mut total = 0; + let func = (|a: i32, b: i32| total = a + b).into_closure_mut(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + func.call_once(args).unwrap(); + assert_eq!(total, 100); + } + + #[test] + fn should_create_dynamic_closure_mut_from_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let mut func = add.into_closure_mut(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&100)); + } + + #[test] + fn should_default_with_closure_type_name() { + let mut total = 0; + let func = (|a: i32, b: i32| total = a + b).into_closure_mut(); + assert_eq!( + func.info().name(), + Some("bevy_reflect::func::closures::into_closure_mut::tests::should_default_with_closure_type_name::{{closure}}") + ); + } +} diff --git a/crates/bevy_reflect/src/func/closures/mod.rs b/crates/bevy_reflect/src/func/closures/mod.rs index c2c9a861fbc9d..cdba3bab061c8 100644 --- a/crates/bevy_reflect/src/func/closures/mod.rs +++ b/crates/bevy_reflect/src/func/closures/mod.rs @@ -1,5 +1,9 @@ -pub use closure::*; +pub use dynamic_closure::*; +pub use dynamic_closure_mut::*; pub use into_closure::*; +pub use into_closure_mut::*; -mod closure; +mod dynamic_closure; +mod dynamic_closure_mut; mod into_closure; +mod into_closure_mut; diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index 63e5b0b3ee343..a84e5dca1f895 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -208,3 +208,26 @@ impl IntoFunction<()> for DynamicFunction { self } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_overwrite_function_name() { + fn foo() {} + + let func = foo.into_function().with_name("my_function"); + assert_eq!(func.info().name(), Some("my_function")); + } + + #[test] + fn should_convert_dynamic_function_with_into_function() { + fn make_function, M>(f: F) -> DynamicFunction { + f.into_function() + } + + let function: DynamicFunction = make_function(|| {}); + let _: DynamicFunction = make_function(function); + } +} diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index 0f128bda24487..36d77cc47072f 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -1,10 +1,12 @@ +use std::panic::{RefUnwindSafe, UnwindSafe}; + use crate::func::function::DynamicFunction; use crate::func::{ReflectFn, TypedFunction}; /// A trait for types that can be converted into a [`DynamicFunction`]. /// -/// This trait is automatically implemented for any type with a `'static` lifetime -/// and also implements [`ReflectFn`] and [`TypedFunction`]. +/// This trait is automatically implemented for many standard Rust functions +/// that also implement [`ReflectFn`] and [`TypedFunction`]. /// /// To handle types such as closures that capture references to their environment, /// see [`IntoClosure`] instead. @@ -20,12 +22,160 @@ pub trait IntoFunction { impl IntoFunction<(Marker1, Marker2)> for F where - F: ReflectFn<'static, Marker1> + TypedFunction + 'static, + F: ReflectFn<'static, Marker1> + + TypedFunction + // Ideally, we'd only implement `IntoFunction` on actual `fn` types, + // but this would only work if users first explicitly coerced their functions + // to a function pointer, which is not the best user experience. + // So as a compromise, we'll stick to allowing any type that implements + // `ReflectFn` and `TypedFunction`, but also add the following trait bounds + // that all `fn` types implement: + + Clone + + Copy + + Send + + Sync + + Unpin + + UnwindSafe + + RefUnwindSafe + + 'static, { fn into_function(self) -> DynamicFunction { + // Note that to further guarantee that `self` is a true `fn` type, + // we could add a compile time assertion that `F` is zero-sized. + // However, we don't do this because it would prevent users from + // converting function pointers into `DynamicFunction`s. + DynamicFunction::new( move |args, info| self.reflect_call(args, info), Self::function_info(), ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate as bevy_reflect; + use crate::func::ArgList; + use bevy_reflect_derive::Reflect; + + #[test] + fn should_create_dynamic_function_from_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let func = add.into_function(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&100)); + } + + #[test] + fn should_create_dynamic_function_from_function_pointer() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let func = (add as fn(i32, i32) -> i32).into_function(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&100)); + } + + #[test] + fn should_create_dynamic_function_from_anonymous_function() { + let func = (|a: i32, b: i32| a + b).into_function(); + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&100)); + } + + #[test] + fn should_create_dynamic_function_from_method() { + #[derive(Reflect, Debug, PartialEq)] + struct Foo(i32); + + impl Foo { + pub fn add(&self, other: &Foo) -> Foo { + Foo(self.0 + other.0) + } + } + + let foo_a = Foo(25); + let foo_b = Foo(75); + + let func = Foo::add.into_function(); + let args = ArgList::new().push_ref(&foo_a).push_ref(&foo_b); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.downcast_ref::(), Some(&Foo(100))); + } + + #[test] + fn should_allow_zero_args() { + fn foo() -> String { + String::from("Hello, World!") + } + + let func = foo.into_function(); + let args = ArgList::new(); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!( + result.downcast_ref::(), + Some(&String::from("Hello, World!")) + ); + } + + #[test] + fn should_allow_unit_return() { + fn foo(_: i32) {} + + let func = foo.into_function(); + let args = ArgList::new().push_owned(123_i32); + let result = func.call(args).unwrap(); + assert!(result.is_unit()); + } + + #[test] + fn should_allow_reference_return() { + fn foo<'a>(value: &'a i32, _: String, _: &bool) -> &'a i32 { + value + } + + let value: i32 = 123; + let func = foo.into_function(); + let args = ArgList::new() + .push_ref(&value) + .push_owned(String::from("Hello, World!")) + .push_ref(&true); + let result = func.call(args).unwrap().unwrap_ref(); + assert_eq!(result.downcast_ref::(), Some(&123)); + } + + #[test] + fn should_allow_mutable_reference_return() { + fn foo<'a>(value: &'a mut i32, _: String, _: &bool) -> &'a mut i32 { + value + } + + let mut value: i32 = 123; + let func = foo.into_function(); + let args = ArgList::new() + .push_mut(&mut value) + .push_owned(String::from("Hello, World!")) + .push_ref(&true); + let result = func.call(args).unwrap().unwrap_mut(); + assert_eq!(result.downcast_mut::(), Some(&mut 123)); + } + + #[test] + fn should_default_with_function_type_name() { + fn foo() {} + + let func = foo.into_function(); + assert_eq!( + func.info().name(), + Some("bevy_reflect::func::into_function::tests::should_default_with_function_type_name::foo") + ); + } +} diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index c46873bd519b7..59704c6234a3c 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -1,10 +1,10 @@ //! Reflection-based dynamic functions. //! //! This module provides a way to pass around and call functions dynamically -//! using the [`DynamicFunction`] and [`DynamicClosure`] types. +//! using the [`DynamicFunction`], [`DynamicClosure`], and [`DynamicClosureMut`] types. //! -//! Many simple functions can be automatically converted to [`DynamicFunction`] or [`DynamicClosure`] -//! using the [`IntoFunction`] or [`IntoClosure`] traits. +//! Many simple functions and closures can be automatically converted to these types +//! using the [`IntoFunction`], [`IntoClosure`], and [`IntoClosureMut`] traits, respectively. //! //! Once this dynamic representation is created, it can be called with a set of arguments provided //! via an [`ArgList`]. @@ -12,7 +12,6 @@ //! This returns a [`FunctionResult`] containing the [`Return`] value, //! which can be used to extract a [`Reflect`] trait object. //! -//! //! # Example //! //! ``` @@ -34,31 +33,44 @@ //! assert_eq!(value.unwrap_owned().downcast_ref::(), Some(&100)); //! ``` //! -//! # Function vs Closure +//! # Functions vs Closures //! //! In Rust, a "function" is any callable that does not capture its environment. //! These are typically defined with the `fn` keyword, but may also use anonymous function syntax. //! -//! Rust also has the concept of "closures", which are functions that capture their environment. +//! ```rust +//! // This is a standard Rust function: +//! fn add(a: i32, b: i32) -> i32 { +//! a + b +//! } +//! +//! // This is an anonymous Rust function: +//! let add = |a: i32, b: i32| a + b; +//! ``` +//! +//! Rust also has the concept of "closures", which are special functions that capture their environment. //! These are always defined with anonymous function syntax. //! -//! For the purposes of reflection, this module alters those definitions a bit. -//! Rather than placing the distinction on whether the callable _captures_ its environment, -//! we place it on whether it _references_ its environment. +//! ```rust +//! // A closure that captures an immutable reference to a variable +//! let c = 123; +//! let add = |a: i32, b: i32| a + b + c; //! -//! This means that a "reflected function" is a function or closure that either does not -//! capture any variables or takes ownership of all captured variables. -//! In other words, it has a `'static` lifetime. +//! // A closure that captures a mutable reference to a variable +//! let mut total = 0; +//! let add = |a: i32, b: i32| total += a + b; //! -//! A "reflected closure", on the other hand, is a closure that captures references to its environment. +//! // A closure that takes ownership of its captured variables by moving them +//! let c = 123; +//! let add = move |a: i32, b: i32| a + b + c; +//! ``` //! -//! This difference from the Rust definitions exists in order to make reflected functions more flexible. +//! Each callable may be considered a subset of the other: +//! functions are a subset of immutable closures which are a subset of mutable closures. //! -//! An example of where this might be useful is creating a [`DynamicFunction`] for a scripting language, -//! where you want to inject some runtime state into the function without changing its signature for consumers. -//! By Rust's definitions, this would only be possible for closures. -//! And while this would still be defined as a closure at compile time, -//! it would be seen by users of the scripting language as a function at runtime. +//! This means that, in terms of traits, you could imagine that any type that implements +//! [`IntoFunction`], also implements [`IntoClosure`] and [`IntoClosureMut`]. +//! And every type that implements [`IntoClosure`] also implements [`IntoClosureMut`]. //! //! # Valid Signatures //! @@ -87,6 +99,7 @@ //! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ //! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check +pub use args::{Arg, ArgError, ArgList}; pub use closures::*; pub use error::*; pub use function::*; @@ -96,8 +109,6 @@ pub use reflect_fn::*; pub use reflect_fn_mut::*; pub use return_type::*; -pub use args::{Arg, ArgError, ArgList}; - pub mod args; mod closures; mod error; @@ -111,168 +122,12 @@ mod return_type; #[cfg(test)] mod tests { - use super::*; - use crate as bevy_reflect; - use crate::func::args::{ArgError, ArgId, ArgList, Ownership}; - use crate::{Reflect, TypePath}; use alloc::borrow::Cow; - #[test] - fn should_create_dynamic_function() { - fn add(a: i32, b: i32) -> i32 { - a + b - } - - let func = add.into_function(); - let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!(result.downcast_ref::(), Some(&100)); - } - - #[test] - fn should_create_dynamic_function_from_closure() { - let c = 23; - let func = (move |a: i32, b: i32| a + b + c).into_function(); - let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!(result.downcast_ref::(), Some(&123)); - } - - #[test] - fn should_create_dynamic_closure() { - let mut func = (|a: i32, b: i32| a + b).into_closure(); - let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!(result.downcast_ref::(), Some(&100)); - } - - #[test] - fn should_create_dynamic_closure_from_function() { - fn add(a: i32, b: i32) -> i32 { - a + b - } - - let mut func = add.into_closure(); - let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!(result.downcast_ref::(), Some(&100)); - } - - #[test] - fn should_create_dynamic_closure_with_capture() { - let mut total = 0; - let func = (|a: i32, b: i32| total = a + b).into_closure(); - let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); - func.call_once(args).unwrap(); - assert_eq!(total, 100); - } - - #[test] - fn should_create_dynamic_method() { - #[derive(Reflect, Debug, PartialEq)] - struct Foo(i32); - - impl Foo { - pub fn add(&self, other: &Foo) -> Foo { - Foo(self.0 + other.0) - } - } - - let foo_a = Foo(25); - let foo_b = Foo(75); - - let func = Foo::add.into_function(); - let args = ArgList::new().push_ref(&foo_a).push_ref(&foo_b); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!(result.downcast_ref::(), Some(&Foo(100))); - } - - #[test] - fn should_allow_zero_args() { - fn foo() -> String { - String::from("Hello, World!") - } - - let func = foo.into_function(); - let args = ArgList::new(); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!( - result.downcast_ref::(), - Some(&String::from("Hello, World!")) - ); - } - - #[test] - fn should_allow_unit_return() { - fn foo(_: i32) {} - - let func = foo.into_function(); - let args = ArgList::new().push_owned(123_i32); - let result = func.call(args).unwrap(); - assert!(result.is_unit()); - } - - #[test] - fn should_allow_reference_return() { - fn foo<'a>(value: &'a i32, _: String, _: &bool) -> &'a i32 { - value - } - - let value: i32 = 123; - let func = foo.into_function(); - let args = ArgList::new() - .push_ref(&value) - .push_owned(String::from("Hello, World!")) - .push_ref(&true); - let result = func.call(args).unwrap().unwrap_ref(); - assert_eq!(result.downcast_ref::(), Some(&123)); - } - - #[test] - fn should_allow_mutable_reference_return() { - fn foo<'a>(value: &'a mut i32, _: String, _: &bool) -> &'a mut i32 { - value - } - - let mut value: i32 = 123; - let func = foo.into_function(); - let args = ArgList::new() - .push_mut(&mut value) - .push_owned(String::from("Hello, World!")) - .push_ref(&true); - let result = func.call(args).unwrap().unwrap_mut(); - assert_eq!(result.downcast_mut::(), Some(&mut 123)); - } - - #[test] - fn should_default_with_function_type_name() { - fn foo() {} - - let func = foo.into_function(); - assert_eq!( - func.info().name(), - Some("bevy_reflect::func::tests::should_default_with_function_type_name::foo") - ); - } - - #[test] - fn should_default_with_closure_type_name() { - let bar = |_: i32| {}; - - let func = bar.into_function(); - assert_eq!( - func.info().name(), - Some("bevy_reflect::func::tests::should_default_with_closure_type_name::{{closure}}") - ); - } - - #[test] - fn should_overwrite_function_name() { - fn foo() {} + use crate::func::args::{ArgError, ArgId, ArgList, Ownership}; + use crate::TypePath; - let func = foo.into_function().with_name("my_function"); - assert_eq!(func.info().name(), Some("my_function")); - } + use super::*; #[test] fn should_error_on_missing_args() { @@ -339,14 +194,4 @@ mod tests { }) ); } - - #[test] - fn should_convert_dynamic_function_with_into_function() { - fn make_function, M>(f: F) -> DynamicFunction { - f.into_function() - } - - let function: DynamicFunction = make_function(|| {}); - let _: DynamicFunction = make_function(function); - } } diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs index 91316e2462a1d..35cbb966a2101 100644 --- a/crates/bevy_reflect/src/func/reflect_fn_mut.rs +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -9,7 +9,7 @@ use crate::Reflect; /// /// This allows functions to be called dynamically through [reflection]. /// -/// It is a supertrait of [`ReflectFn`], and is used for closures that may mutate their environment. +/// This is a supertrait of [`ReflectFn`], and is used for closures that may mutate their environment. /// /// # Blanket Implementation /// diff --git a/examples/reflection/function_reflection.rs b/examples/reflection/function_reflection.rs index d9e66add4573b..4d1faf7c1a77a 100644 --- a/examples/reflection/function_reflection.rs +++ b/examples/reflection/function_reflection.rs @@ -8,8 +8,8 @@ use bevy::reflect::func::args::ArgInfo; use bevy::reflect::func::{ - ArgList, DynamicClosure, DynamicFunction, FunctionInfo, IntoClosure, IntoFunction, Return, - ReturnInfo, + ArgList, DynamicClosure, DynamicClosureMut, DynamicFunction, FunctionInfo, IntoClosure, + IntoClosureMut, IntoFunction, Return, ReturnInfo, }; use bevy::reflect::Reflect; @@ -56,31 +56,31 @@ fn main() { let value: Box = return_value.unwrap_owned(); assert_eq!(value.take::().unwrap(), 4); - // The same can also be done for closures that capture references to their environment + // The same can also be done for closures that capture references to their environment. + // Closures that capture their environment immutably can be converted into a `DynamicClosure` // using the `IntoClosure` trait. - let mut count = 0; - let increment = |amount: i32| count += amount; - - let closure: DynamicClosure = dbg!(increment.into_closure()); - let args = dbg!(ArgList::new().push_owned(5_i32)); - // `DynamicClosure`s that mutably capture their environment like this one - // may need to be dropped before those captured variables may be used again. - // This can be done manually with `drop(closure)` or by using the `DynamicClosure::call_once` method. - dbg!(closure.call_once(args).unwrap()); - assert_eq!(count, 5); - - // Note that `DynamicFunction` just requires a `'static` lifetime. - // This means that closures that capture variables by taking ownership of them - // can still be converted to a `DynamicFunction`. let minimum = 5; - let clamp = move |value: i32| value.max(minimum); + let clamp = |value: i32| value.max(minimum); - let function: DynamicFunction = dbg!(clamp.into_function()); + let function: DynamicClosure = dbg!(clamp.into_closure()); let args = dbg!(ArgList::new().push_owned(2_i32)); let return_value = dbg!(function.call(args).unwrap()); let value: Box = return_value.unwrap_owned(); assert_eq!(value.take::().unwrap(), 5); + // We can also handle closures that capture their environment mutably + // using the `IntoClosureMut` trait. + let mut count = 0; + let increment = |amount: i32| count += amount; + + let closure: DynamicClosureMut = dbg!(increment.into_closure_mut()); + let args = dbg!(ArgList::new().push_owned(5_i32)); + // Because `DynamicClosureMut` mutably borrows `total`, + // it will need to be dropped before `total` can be accessed again. + // This can be done manually with `drop(closure)` or by using the `DynamicClosureMut::call_once` method. + dbg!(closure.call_once(args).unwrap()); + assert_eq!(count, 5); + // As stated before, this works for many kinds of simple functions. // Functions with non-reflectable arguments or return values may not be able to be converted. // Generic functions are also not supported (unless manually monomorphized like `foo::.into_function()`). From fb58ba7f8fecd25dd3a1acc8ea704f8a97830886 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 15 Jul 2024 00:00:50 -0700 Subject: [PATCH 09/10] Rename `count_tts` -> `count_tokens` --- crates/bevy_reflect/src/func/macros.rs | 8 ++++---- crates/bevy_reflect/src/func/reflect_fn.rs | 10 +++++----- crates/bevy_reflect/src/func/reflect_fn_mut.rs | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs index 0787a1eb83d70..3fb93a2230610 100644 --- a/crates/bevy_reflect/src/func/macros.rs +++ b/crates/bevy_reflect/src/func/macros.rs @@ -103,10 +103,10 @@ pub(crate) use impl_function_traits; /// See [here] for details. /// /// [here]: https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html#bit-twiddling -macro_rules! count_tts { +macro_rules! count_tokens { () => { 0 }; - ($odd:tt $($a:tt $b:tt)*) => { ($crate::func::macros::count_tts!($($a)*) << 1) | 1 }; - ($($a:tt $even:tt)*) => { $crate::func::macros::count_tts!($($a)*) << 1 }; + ($odd:tt $($a:tt $b:tt)*) => { ($crate::func::macros::count_tokens!($($a)*) << 1) | 1 }; + ($($a:tt $even:tt)*) => { $crate::func::macros::count_tokens!($($a)*) << 1 }; } -pub(crate) use count_tts; +pub(crate) use count_tokens; diff --git a/crates/bevy_reflect/src/func/reflect_fn.rs b/crates/bevy_reflect/src/func/reflect_fn.rs index d84a5ddb6864d..20686a725f789 100644 --- a/crates/bevy_reflect/src/func/reflect_fn.rs +++ b/crates/bevy_reflect/src/func/reflect_fn.rs @@ -1,7 +1,7 @@ use bevy_utils::all_tuples; use crate::func::args::FromArg; -use crate::func::macros::count_tts; +use crate::func::macros::count_tokens; use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionResult, IntoReturn, ReflectFnMut}; use crate::Reflect; @@ -85,7 +85,7 @@ macro_rules! impl_reflect_fn { Function: for<'a> Fn($($Arg::Item<'a>),*) -> ReturnType + 'env, { fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!($($Arg)*); + const COUNT: usize = count_tokens!($($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { @@ -120,7 +120,7 @@ macro_rules! impl_reflect_fn { Function: for<'a> Fn(&'a Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, { fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); + const COUNT: usize = count_tokens!(Receiver $($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { @@ -157,7 +157,7 @@ macro_rules! impl_reflect_fn { Function: for<'a> Fn(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut ReturnType + 'env, { fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); + const COUNT: usize = count_tokens!(Receiver $($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { @@ -194,7 +194,7 @@ macro_rules! impl_reflect_fn { Function: for<'a> Fn(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, { fn reflect_call<'a>(&self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); + const COUNT: usize = count_tokens!(Receiver $($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs index 35cbb966a2101..db24d8dd8a7cf 100644 --- a/crates/bevy_reflect/src/func/reflect_fn_mut.rs +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -1,7 +1,7 @@ use bevy_utils::all_tuples; use crate::func::args::FromArg; -use crate::func::macros::count_tts; +use crate::func::macros::count_tokens; use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionResult, IntoReturn}; use crate::Reflect; @@ -95,7 +95,7 @@ macro_rules! impl_reflect_fn_mut { Function: for<'a> FnMut($($Arg::Item<'a>),*) -> ReturnType + 'env, { fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!($($Arg)*); + const COUNT: usize = count_tokens!($($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { @@ -130,7 +130,7 @@ macro_rules! impl_reflect_fn_mut { Function: for<'a> FnMut(&'a Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, { fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); + const COUNT: usize = count_tokens!(Receiver $($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { @@ -167,7 +167,7 @@ macro_rules! impl_reflect_fn_mut { Function: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut ReturnType + 'env, { fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); + const COUNT: usize = count_tokens!(Receiver $($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { @@ -204,7 +204,7 @@ macro_rules! impl_reflect_fn_mut { Function: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a ReturnType + 'env, { fn reflect_call_mut<'a>(&mut self, args: ArgList<'a>, _info: &FunctionInfo) -> FunctionResult<'a> { - const COUNT: usize = count_tts!(Receiver $($Arg)*); + const COUNT: usize = count_tokens!(Receiver $($Arg)*); if args.len() != COUNT { return Err(FunctionError::InvalidArgCount { From 933c9f41b6a53b5019a2321151e30f3144e35871 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 15 Jul 2024 18:39:21 -0700 Subject: [PATCH 10/10] Fix up docs Avoid using function-pointer syntax when discussin general function signatures. Also fixes a broken doc link --- crates/bevy_reflect/src/func/info.rs | 9 +++++---- crates/bevy_reflect/src/func/into_function.rs | 6 ++++-- crates/bevy_reflect/src/func/mod.rs | 8 ++++---- crates/bevy_reflect/src/func/reflect_fn.rs | 8 ++++---- crates/bevy_reflect/src/func/reflect_fn_mut.rs | 8 ++++---- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 789b768219bf6..6793b654a49cc 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -175,6 +175,7 @@ impl ReturnInfo { /// [unconstrained type parameters] when defining impls with generic arguments or return types. /// This `Marker` can be any type, provided it doesn't conflict with other implementations. /// +/// [module-level documentation]: crate::func /// [`Typed`]: crate::Typed pub trait TypedFunction { /// Get the [`FunctionInfo`] for this type. @@ -189,10 +190,10 @@ pub trait TypedFunction { /// Helper macro for implementing [`TypedFunction`] on Rust closures. /// /// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): -/// - `fn(arg0, arg1, ..., argN) -> R` -/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +/// - `FnMut(arg0, arg1, ..., argN) -> R` +/// - `FnMut(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `FnMut(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `FnMut(&mut Receiver, arg0, arg1, ..., argN) -> &R` macro_rules! impl_typed_function { ($(($Arg:ident, $arg:ident)),*) => { // === (...) -> ReturnType === // diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index 36d77cc47072f..7104eb1a72325 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -24,9 +24,11 @@ impl IntoFunction<(Marker1, Marker2)> for F where F: ReflectFn<'static, Marker1> + TypedFunction - // Ideally, we'd only implement `IntoFunction` on actual `fn` types, + // Ideally, we'd only implement `IntoFunction` on actual function types + // (i.e. functions that do not capture their environment at all), // but this would only work if users first explicitly coerced their functions - // to a function pointer, which is not the best user experience. + // to a function pointer like `(add as fn(i32, i32) -> i32).into_function()`, + // which is certainly not the best user experience. // So as a compromise, we'll stick to allowing any type that implements // `ReflectFn` and `TypedFunction`, but also add the following trait bounds // that all `fn` types implement: diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index 59704c6234a3c..0c741cf048087 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -77,10 +77,10 @@ //! Many of the traits in this module have default blanket implementations over a specific set of function signatures. //! //! These signatures are: -//! - `fn(...) -> R` -//! - `fn<'a>(&'a arg, ...) -> &'a R` -//! - `fn<'a>(&'a mut arg, ...) -> &'a R` -//! - `fn<'a>(&'a mut arg, ...) -> &'a mut R` +//! - `(...) -> R` +//! - `for<'a> (&'a arg, ...) -> &'a R` +//! - `for<'a> (&'a mut arg, ...) -> &'a R` +//! - `for<'a> (&'a mut arg, ...) -> &'a mut R` //! //! Where `...` represents 0 to 15 arguments (inclusive) of the form `T`, `&T`, or `&mut T`. //! The lifetime of any reference to the return type `R`, must be tied to a "receiver" argument diff --git a/crates/bevy_reflect/src/func/reflect_fn.rs b/crates/bevy_reflect/src/func/reflect_fn.rs index 20686a725f789..7a80f4cbd1cbc 100644 --- a/crates/bevy_reflect/src/func/reflect_fn.rs +++ b/crates/bevy_reflect/src/func/reflect_fn.rs @@ -68,10 +68,10 @@ pub trait ReflectFn<'env, Marker>: ReflectFnMut<'env, Marker> { /// Helper macro for implementing [`ReflectFn`] on Rust closures. /// /// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): -/// - `fn(arg0, arg1, ..., argN) -> R` -/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +/// - `Fn(arg0, arg1, ..., argN) -> R` +/// - `Fn(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `Fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `Fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` macro_rules! impl_reflect_fn { ($(($Arg:ident, $arg:ident)),*) => { // === (...) -> ReturnType === // diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs index db24d8dd8a7cf..a396d562620ba 100644 --- a/crates/bevy_reflect/src/func/reflect_fn_mut.rs +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -78,10 +78,10 @@ pub trait ReflectFnMut<'env, Marker> { /// Helper macro for implementing [`ReflectFnMut`] on Rust closures. /// /// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): -/// - `fn(arg0, arg1, ..., argN) -> R` -/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` -/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +/// - `FnMut(arg0, arg1, ..., argN) -> R` +/// - `FnMut(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `FnMut(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `FnMut(&mut Receiver, arg0, arg1, ..., argN) -> &R` macro_rules! impl_reflect_fn_mut { ($(($Arg:ident, $arg:ident)),*) => { // === (...) -> ReturnType === //