Skip to content

bevy_reflect: Add DynamicClosure and DynamicClosureMut #14141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 5 additions & 5 deletions crates/bevy_reflect/derive/src/impls/func/into_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_reflect/src/func/args/arg.rs
Original file line number Diff line number Diff line change
@@ -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>),
Expand Down
13 changes: 9 additions & 4 deletions crates/bevy_reflect/src/func/args/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_reflect/src/func/args/list.rs
Original file line number Diff line number Diff line change
@@ -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
///
Expand All @@ -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>>);

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_reflect/src/func/args/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
180 changes: 180 additions & 0 deletions crates/bevy_reflect/src/func/closures/dynamic_closure.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading