bevy_reflect: Add DynamicClosure and DynamicClosureMut (#14141)

# Objective

As mentioned in
[this](https://github.com/bevyengine/bevy/pull/13152#issuecomment-2198387297)
comment, creating a function registry (see #14098) is a bit difficult
due to the requirements of `DynamicFunction`. Internally, a
`DynamicFunction` contains a `Box<dyn FnMut>` (the function that reifies
reflected arguments and calls the actual function), which requires `&mut
self` in order to be called.

This means that users would require a mutable reference to the function
registry for it to be useful— which isn't great. And they can't clone
the `DynamicFunction` either because cloning an `FnMut` isn't really
feasible (wrapping it in an `Arc` would allow it to be cloned but we
wouldn't be able to call the clone since we need a mutable reference to
the `FnMut`, which we can't get with multiple `Arc`s still alive,
requiring us to also slap in a `Mutex`, which adds additional overhead).

And we don't want to just replace the `dyn FnMut` with `dyn Fn` as that
would prevent reflecting closures that mutate their environment.

Instead, we need to introduce a new type to split the requirements of
`DynamicFunction`.

## Solution

Introduce new types for representing closures.

Specifically, this PR introduces `DynamicClosure` and
`DynamicClosureMut`. Similar to how `IntoFunction` exists for
`DynamicFunction`, two new traits were introduced: `IntoClosure` and
`IntoClosureMut`.

Now `DynamicFunction` stores a `dyn Fn` with a `'static` lifetime.
`DynamicClosure` also uses a `dyn Fn` but has a lifetime, `'env`, tied
to its environment. `DynamicClosureMut` is most like the old
`DynamicFunction`, keeping the `dyn FnMut` and also typing its lifetime,
`'env`, to the environment

Here are some comparison tables:

|   | `DynamicFunction` | `DynamicClosure` | `DynamicClosureMut` |
| - | ----------------- | ---------------- | ------------------- |
| Callable with `&self` |  |  |  |
| Callable with `&mut self` |  |  |  |
| Allows for non-`'static` lifetimes |  |  |  |

|   | `IntoFunction` | `IntoClosure` | `IntoClosureMut` |
| - | -------------- | ------------- | ---------------- |
| Convert `fn` functions |  |  |  |
| Convert `fn` methods |  |  |  |
| Convert anonymous functions |  |  |  |
| Convert closures that capture immutable references |  |  |  |
| Convert closures that capture mutable references |  |  |  |
| Convert closures that capture owned values | [^1] |  |  |

[^1]: Due to limitations in Rust, `IntoFunction` can't be implemented
for just functions (unless we forced users to manually coerce them to
function pointers first). So closures that meet the trait requirements
_can technically_ be converted into a `DynamicFunction` as well. To both
future-proof and reduce confusion, though, we'll just pretend like this
isn't a thing.

```rust
let mut list: Vec<i32> = 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_once(args).unwrap().unwrap_owned();

// Check the result:
assert_eq!(value.take::<i32>().unwrap(), 2);
assert_eq!(list, vec![1, -2, 3]);
```

### `ReflectFn`/`ReflectFnMut`

To make extending the function reflection system easier (the blanket
impls for `IntoFunction`, `IntoClosure`, and `IntoClosureMut` are all
incredibly short), this PR generalizes callables with two new traits:
`ReflectFn` and `ReflectFnMut`.

These traits mimic `Fn` and `FnMut` but allow for being called via
reflection. In fact, their blanket implementations are identical save
for `ReflectFn` being implemented over `Fn` types and `ReflectFnMut`
being implemented over `FnMut` types.

And just as `Fn` is a subtrait of `FnMut`, `ReflectFn` is a subtrait of
`ReflectFnMut`. So anywhere that expects a `ReflectFnMut` can also be
given a `ReflectFn`.

To reiterate, these traits aren't 100% necessary. They were added in
purely for extensibility. If we decide to split things up differently or
add new traits/types in the future, then those changes should be much
simpler to implement.

### `TypedFunction`

Because of the split into `ReflectFn` and `ReflectFnMut`, we needed a
new way to access the function type information. This PR moves that
concept over into `TypedFunction`.

Much like `Typed`, this provides a way to access a function's
`FunctionInfo`.

By splitting this trait out, it helps to ensure the other traits are
focused on a single responsibility.

### Internal Macros

The original function PR (#13152) implemented `IntoFunction` using a
macro which was passed into an `all_tuples!` macro invocation. Because
we needed the same functionality for these new traits, this PR has
copy+pasted that code for `ReflectFn`, `ReflectFnMut`, and
`TypedFunction`— albeit with some differences between them.

Originally, I was going to try and macro-ify the impls and where clauses
such that we wouldn't have to straight up duplicate a lot of this logic.
However, aside from being more complex in general, autocomplete just
does not play nice with such heavily nested macros (tried in both
RustRover and VSCode). And both of those problems told me that it just
wasn't worth it: we need to ensure the crate is easily maintainable,
even at the cost of duplicating code.

So instead, I made sure to simplify the macro code by removing all
fully-qualified syntax and cutting the where clauses down to the bare
essentials, which helps to clean up a lot of the visual noise. I also
tried my best to document the macro logic in certain areas (I may even
add a bit more) to help with maintainability for future devs.

### Documentation

Documentation for this module was a bit difficult for me. So many of
these traits and types are very interconnected. And each trait/type has
subtle differences that make documenting it in a single place, like at
the module level, difficult to do cleanly. Describing the valid
signatures is also challenging to do well.

Hopefully what I have here is okay. I think I did an okay job, but let
me know if there any thoughts on ways to improve it. We can also move
such a task to a followup PR for more focused discussion.

## Testing

You can test locally by running:

```
cargo test --package bevy_reflect
```

---

## Changelog

- Added `DynamicClosure` struct
- Added `DynamicClosureMut` struct
- Added `IntoClosure` trait
- Added `IntoClosureMut` trait
- Added `ReflectFn` trait
- Added `ReflectFnMut` trait
- Added `TypedFunction` trait
- `IntoFunction` now only works for standard Rust functions
- `IntoFunction` no longer takes a lifetime parameter
- `DynamicFunction::call` now only requires `&self`
- Removed `DynamicFunction::call_once`
- Changed the `IntoReturn::into_return` signature to include a where
clause

## Internal Migration Guide

> [!important]
> Function reflection was introduced as part of the 0.15 dev cycle. This
migration guide was written for developers relying on `main` during this
cycle, and is not a breaking change coming from 0.14.

### `IntoClosure`

`IntoFunction` now only works for standard Rust functions. Calling
`IntoFunction::into_function` on a closure that captures references to
its environment (either mutable or immutable), will no longer compile.

Instead, you will need to use either `IntoClosure::into_closure` to
create a `DynamicClosure` or `IntoClosureMut::into_closure_mut` to
create a `DynamicClosureMut`, depending on your needs:

```rust
let punct = String::from("!");
let print = |value: String| {
    println!("{value}{punct}");
};

// BEFORE
let func: DynamicFunction = print.into_function();

// AFTER
let func: DynamicClosure = print.into_closure();
```

### `IntoFunction` lifetime

Additionally, `IntoFunction` no longer takes a lifetime parameter as it
always expects a `'static` lifetime. Usages will need to remove any
lifetime parameters:

```rust
// BEFORE
fn execute<'env, F: IntoFunction<'env, Marker>, Marker>(f: F) {/* ... */}

// AFTER
fn execute<F: IntoFunction<Marker>, Marker>(f: F) {/* ... */}
```

### `IntoReturn`

`IntoReturn::into_return` now has a where clause. Any manual
implementors will need to add this where clause to their implementation.
This commit is contained in:
Gino Valente 2024-07-15 20:22:43 -07:00 committed by GitHub
parent 20c6bcdba4
commit 1042f09c2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1604 additions and 528 deletions

View file

@ -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
}

View file

@ -0,0 +1,19 @@
#![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);
let _ = closure_capture_owned.into_function();
//~^ E0277
let value = String::from("Hello, World!");
let closure_capture_reference = || println!("{}", value);
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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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<dyn Reflect>),

View file

@ -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::closures::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()
}

View file

@ -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<Arg<'a>>);

View file

@ -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::*;

View file

@ -0,0 +1,180 @@
use alloc::borrow::Cow;
use core::fmt::{Debug, Formatter};
use crate::func::args::{ArgInfo, ArgList};
use crate::func::info::FunctionInfo;
use crate::func::{FunctionResult, IntoClosure, ReturnInfo};
/// A dynamic representation of a Rust 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`].
///
/// 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 punct = String::from("!!!");
///
/// let punctuate = |text: &String| -> String {
/// format!("{}{}", text, punct)
/// };
///
/// // Convert the closure into a dynamic closure using `IntoClosure::into_closure`
/// let mut func: DynamicClosure = punctuate.into_closure();
///
/// // Dynamically call the closure:
/// 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::<String>().unwrap(), "Hello, world!!!");
/// ```
///
/// [`DynamicClosureMut`]: crate::func::closures::DynamicClosureMut
/// [`DynamicFunction`]: crate::func::DynamicFunction
pub struct DynamicClosure<'env> {
info: FunctionInfo,
func: Box<dyn for<'a> Fn(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<F: for<'a> Fn(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<Cow<'static, str>>) -> 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<ArgInfo>) -> 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 c = 23;
/// let add = |a: i32, b: i32| -> i32 {
/// 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::<i32>().unwrap(), 123);
/// ```
pub fn call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> {
(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
}
}
#[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);
}
}

View file

@ -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<i32> = 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::<i32>().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<dyn for<'a> 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<F: for<'a> 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<Cow<'static, str>>) -> 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<ArgInfo>) -> 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::<i32>().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);
}
}

View file

@ -0,0 +1,66 @@
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
/// [`ReflectFn`] and [`TypedFunction`].
///
/// This trait can be seen as a supertrait of [`IntoFunction`].
///
/// See the [module-level documentation] for more information.
///
/// [`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: ReflectFn<'env, Marker1> + TypedFunction<Marker2> + 'env,
{
fn into_closure(self) -> DynamicClosure<'env> {
DynamicClosure::new(
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::<i32>(), 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::<i32>(), 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}}")
);
}
}

View file

@ -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<Marker2> + '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::<i32>(), 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::<i32>(), 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}}")
);
}
}

View file

@ -0,0 +1,9 @@
pub use dynamic_closure::*;
pub use dynamic_closure_mut::*;
pub use into_closure::*;
pub use into_closure_mut::*;
mod dynamic_closure;
mod dynamic_closure_mut;
mod into_closure;
mod into_closure_mut;

View file

@ -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<Return<'a>, FunctionError>;

View file

@ -1,21 +1,23 @@
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;
use std::sync::Arc;
/// 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<Return<'a>, FunctionError>;
use crate::func::args::{ArgInfo, ArgList};
use crate::func::info::FunctionInfo;
use crate::func::{FunctionResult, IntoFunction, ReturnInfo};
/// 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 +92,28 @@ pub type FunctionResult<'a> = Result<Return<'a>, 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<dyn for<'a> FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>,
func: Arc<dyn for<'a> 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<F: for<'a> FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>(
pub fn new<F: for<'a> Fn(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'static>(
func: F,
info: FunctionInfo,
) -> Self {
Self {
info,
func: Box::new(func),
func: Arc::new(func),
}
}
@ -144,41 +149,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::<i32>().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 +173,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 +193,41 @@ 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
}
}
#[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<F: IntoFunction<M>, M>(f: F) -> DynamicFunction {
f.into_function()
}
let function: DynamicFunction = make_function(|| {});
let _: DynamicFunction = make_function(function);
}
}

View file

@ -1,10 +1,17 @@
use crate::func::args::{ArgInfo, GetOwnership, Ownership};
use crate::TypePath;
use alloc::borrow::Cow;
/// Type information for a [`DynamicFunction`].
use bevy_utils::all_tuples;
use crate::func::args::{ArgInfo, GetOwnership, Ownership};
use crate::TypePath;
/// 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<Cow<'static, str>>,
@ -24,6 +31,14 @@ impl FunctionInfo {
}
}
/// Create a new [`FunctionInfo`] from the given function or closure.
pub fn from<F, Marker>(function: &F) -> Self
where
F: TypedFunction<Marker>,
{
function.get_function_info()
}
/// Set the name of the function.
///
/// Reflected functions are not required to have a name,
@ -52,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()
}
@ -83,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,
@ -111,3 +129,175 @@ 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.
///
/// [module-level documentation]: crate::func
/// [`Typed`]: crate::Typed
pub trait TypedFunction<Marker> {
/// 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`):
/// - `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 === //
impl<$($Arg,)* ReturnType, Function> TypedFunction<fn($($Arg),*) -> [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::<Function>())
.with_args({
#[allow(unused_mut)]
let mut _index = 0;
vec![
$(ArgInfo::new::<$Arg>({
_index += 1;
_index - 1
}),)*
]
})
.with_return_info(ReturnInfo::new::<ReturnType>())
}
}
// === (&self, ...) -> &ReturnType === //
impl<Receiver, $($Arg,)* ReturnType, Function> TypedFunction<fn(&Receiver, $($Arg),*) -> &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::<Function>())
.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<Receiver, $($Arg,)* ReturnType, Function> TypedFunction<fn(&mut Receiver, $($Arg),*) -> &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::<Function>())
.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<Receiver, $($Arg,)* ReturnType, Function> TypedFunction<fn(&mut Receiver, $($Arg),*) -> &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::<Function>())
.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);

View file

@ -1,318 +1,183 @@
use std::panic::{RefUnwindSafe, UnwindSafe};
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 many standard Rust functions
/// that also implement [`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<Marker> {
/// Converts [`Self`] into a [`DynamicFunction`].
fn into_function(self) -> DynamicFunction<'env>;
fn into_function(self) -> DynamicFunction;
}
/// 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 };
impl<F, Marker1, Marker2> IntoFunction<(Marker1, Marker2)> for F
where
F: ReflectFn<'static, Marker1>
+ TypedFunction<Marker2>
// 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 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:
+ 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(),
)
}
}
/// 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)*);
#[cfg(test)]
mod tests {
use super::*;
use crate as bevy_reflect;
use crate::func::ArgList;
use bevy_reflect_derive::Reflect;
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.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::<R>());
#[test]
fn should_create_dynamic_function_from_function() {
fn add(a: i32, b: i32) -> i32 {
a + b
}
$crate::func::DynamicFunction::new(move |args, _info| {
if args.len() != COUNT {
return Err($crate::func::error::FunctionError::InvalidArgCount {
expected: COUNT,
received: args.len(),
});
}
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::<i32>(), Some(&100));
}
let [$($arg,)*] = args.take().try_into().expect("invalid number of arguments");
#[test]
fn should_create_dynamic_function_from_function_pointer() {
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[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)
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::<i32>(), 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::<i32>(), 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)
}
}
// === 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 foo_a = Foo(25);
let foo_b = Foo(75);
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.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>());
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::<Foo>(), Some(&Foo(100)));
}
$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::<Receiver>(_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)
}
#[test]
fn should_allow_zero_args() {
fn foo() -> String {
String::from("Hello, World!")
}
// === 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 func = foo.into_function();
let args = ArgList::new();
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(
result.downcast_ref::<String>(),
Some(&String::from("Hello, World!"))
);
}
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.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>());
#[test]
fn should_allow_unit_return() {
fn foo(_: i32) {}
$crate::func::DynamicFunction::new(move |args, _info| {
if args.len() != COUNT {
return Err($crate::func::error::FunctionError::InvalidArgCount {
expected: COUNT,
received: args.len(),
});
}
let func = foo.into_function();
let args = ArgList::new().push_owned(123_i32);
let result = func.call(args).unwrap();
assert!(result.is_unit());
}
let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments");
let receiver = receiver.take_mut::<Receiver>(_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)
}
#[test]
fn should_allow_reference_return() {
fn foo<'a>(value: &'a i32, _: String, _: &bool) -> &'a i32 {
value
}
// === 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 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::<i32>(), Some(&123));
}
let info = $crate::func::FunctionInfo::new()
.with_name(std::any::type_name::<F>())
.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::<Receiver>(_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)
}
#[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::<i32>(), 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")
);
}
}
all_tuples!(impl_into_function, 0, 15, Arg, arg);

View file

@ -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_tokens {
() => { 0 };
($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_tokens;

View file

@ -1,18 +1,17 @@
//! Reflection-based dynamic functions.
//!
//! This module provides a way to pass around and call functions dynamically
//! using the [`DynamicFunction`] type.
//! using the [`DynamicFunction`], [`DynamicClosure`], and [`DynamicClosureMut`] types.
//!
//! Many simple functions and closures can be automatically converted to [`DynamicFunction`]
//! using the [`IntoFunction`] trait.
//! Many simple functions and closures can be automatically converted to these types
//! using the [`IntoFunction`], [`IntoClosure`], and [`IntoClosureMut`] traits, respectively.
//!
//! 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,
//! which can be used to extract a [`Reflect`] trait object.
//!
//!
//! # Example
//!
//! ```
@ -34,164 +33,107 @@
//! assert_eq!(value.unwrap_owned().downcast_ref::<i32>(), Some(&100));
//! ```
//!
//! # 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
//! // 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.
//!
//! ```rust
//! // A closure that captures an immutable reference to a variable
//! let c = 123;
//! let add = |a: i32, b: i32| a + b + c;
//!
//! // A closure that captures a mutable reference to a variable
//! let mut total = 0;
//! let add = |a: i32, b: i32| total += a + b;
//!
//! // 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;
//! ```
//!
//! Each callable may be considered a subset of the other:
//! functions are a subset of immutable closures which are a subset of mutable closures.
//!
//! 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
//!
//! Many of the traits in this module have default blanket implementations over a specific set of function signatures.
//!
//! These signatures are:
//! - `(...) -> 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
//! (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 args::{Arg, ArgError, ArgList};
pub use closures::*;
pub use error::*;
pub use function::*;
pub use info::*;
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 closures;
mod error;
mod function;
mod info;
mod into_function;
pub(crate) mod macros;
mod reflect_fn;
mod reflect_fn_mut;
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
}
use crate::func::args::{ArgError, ArgId, ArgList, Ownership};
use crate::TypePath;
let mut 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::<i32>(), Some(&100));
}
#[test]
fn should_create_dynamic_closure() {
let mut 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::<i32>(), Some(&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 mut 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::<Foo>(), Some(&Foo(100)));
}
#[test]
fn should_allow_zero_args() {
fn foo() -> String {
String::from("Hello, World!")
}
let mut func = foo.into_function();
let args = ArgList::new();
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(
result.downcast_ref::<String>(),
Some(&String::from("Hello, World!"))
);
}
#[test]
fn should_allow_unit_return() {
fn foo(_: i32) {}
let mut 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 mut 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::<i32>(), 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 mut 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::<i32>(), 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() {}
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() {
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 +149,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 +165,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 +182,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!(
@ -252,14 +194,4 @@ mod tests {
})
);
}
#[test]
fn should_convert_dynamic_function_with_into_function() {
fn make_function<'a, F: IntoFunction<'a, M>, M>(f: F) -> DynamicFunction<'a> {
f.into_function()
}
let function: DynamicFunction = make_function(|| {});
let _: DynamicFunction = make_function(function);
}
}

View file

@ -0,0 +1,223 @@
use bevy_utils::all_tuples;
use crate::func::args::FromArg;
use crate::func::macros::count_tokens;
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::<i32>().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_tokens!($($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_tokens!(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::<Receiver>(_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_tokens!(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::<Receiver>(_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_tokens!(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::<Receiver>(_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);

View file

@ -0,0 +1,233 @@
use bevy_utils::all_tuples;
use crate::func::args::FromArg;
use crate::func::macros::count_tokens;
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].
///
/// This 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<i32> = 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`):
/// - `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 === //
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_tokens!($($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_tokens!(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::<Receiver>(_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_tokens!(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::<Receiver>(_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_tokens!(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::<Receiver>(_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);

View file

@ -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 `()`).
@ -68,7 +69,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 +114,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 +128,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 +142,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)
}
}

View file

@ -8,7 +8,8 @@
use bevy::reflect::func::args::ArgInfo;
use bevy::reflect::func::{
ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo,
ArgList, DynamicClosure, DynamicClosureMut, DynamicFunction, FunctionInfo, IntoClosure,
IntoClosureMut, 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<dyn Reflect> = return_value.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
// The same can also be done for closures.
// 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 minimum = 5;
let clamp = |value: i32| value.max(minimum);
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<dyn Reflect> = return_value.unwrap_owned();
assert_eq!(value.take::<i32>().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 increment_function: DynamicFunction = dbg!(increment.into_function());
let increment = |amount: i32| count += amount;
let closure: DynamicClosureMut = dbg!(increment.into_closure_mut());
let args = dbg!(ArgList::new().push_owned(5_i32));
// `DynamicFunction`s containing closures that 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());
// 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.
// Generic functions are also not supported (unless manually monomorphized like `foo::<i32>.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];