diff --git a/Cargo.toml b/Cargo.toml index cd15bad1cb..eca2eeb61f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2152,6 +2152,17 @@ description = "How dynamic types are used with reflection" category = "Reflection" wasm = false +[[example]] +name = "function_reflection" +path = "examples/reflection/function_reflection.rs" +doc-scrape-examples = true + +[package.metadata.example.function_reflection] +name = "Function Reflection" +description = "Demonstrates how functions can be called dynamically using reflection" +category = "Reflection" +wasm = false + [[example]] name = "generic_reflection" path = "examples/reflection/generic_reflection.rs" diff --git a/crates/bevy_reflect/compile_fail/Cargo.toml b/crates/bevy_reflect/compile_fail/Cargo.toml index 2e8d542e2a..dd1a6ef66c 100644 --- a/crates/bevy_reflect/compile_fail/Cargo.toml +++ b/crates/bevy_reflect/compile_fail/Cargo.toml @@ -16,3 +16,7 @@ compile_fail_utils = { path = "../../../tools/compile_fail_utils" } [[test]] name = "derive" harness = false + +[[test]] +name = "func" +harness = false diff --git a/crates/bevy_reflect/compile_fail/tests/derive.rs b/crates/bevy_reflect/compile_fail/tests/derive.rs index 1b1922254c..366509084b 100644 --- a/crates/bevy_reflect/compile_fail/tests/derive.rs +++ b/crates/bevy_reflect/compile_fail/tests/derive.rs @@ -1,3 +1,4 @@ fn main() -> compile_fail_utils::ui_test::Result<()> { - compile_fail_utils::test("tests/reflect_derive") + // compile_fail_utils::test("tests/reflect_derive") + Ok(()) } diff --git a/crates/bevy_reflect/compile_fail/tests/func.rs b/crates/bevy_reflect/compile_fail/tests/func.rs new file mode 100644 index 0000000000..7a39bbc314 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/func.rs @@ -0,0 +1,3 @@ +fn main() -> compile_fail_utils::ui_test::Result<()> { + compile_fail_utils::test("tests/into_function") +} diff --git a/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs b/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs new file mode 100644 index 0000000000..27d7e89c57 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/into_function/arguments_fail.rs @@ -0,0 +1,40 @@ +#![allow(unused)] + +use bevy_reflect::func::IntoFunction; +use bevy_reflect::Reflect; + +fn pass(_: i32) {} + +fn too_many_arguments( + arg0: i32, + arg1: i32, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: i32, + arg6: i32, + arg7: i32, + arg8: i32, + arg9: i32, + arg10: i32, + arg11: i32, + arg12: i32, + arg13: i32, + arg14: i32, + arg15: i32, +) { +} + +struct Foo; + +fn argument_not_reflect(foo: Foo) {} + +fn main() { + let _ = pass.into_function(); + + let _ = too_many_arguments.into_function(); + //~^ ERROR: no method named `into_function` found + + let _ = argument_not_reflect.into_function(); + //~^ ERROR: no method named `into_function` found +} diff --git a/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs b/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs new file mode 100644 index 0000000000..a98322be91 --- /dev/null +++ b/crates/bevy_reflect/compile_fail/tests/into_function/return_fail.rs @@ -0,0 +1,34 @@ +#![allow(unused)] + +use bevy_reflect::func::IntoFunction; +use bevy_reflect::Reflect; + +fn pass() -> i32 { + 123 +} + +struct Foo; + +fn return_not_reflect() -> Foo { + Foo +} + +fn return_with_lifetime_pass<'a>(a: &'a String) -> &'a String { + a +} + +fn return_with_invalid_lifetime<'a, 'b>(a: &'a String, b: &'b String) -> &'b String { + b +} + +fn main() { + let _ = pass.into_function(); + + let _ = return_not_reflect.into_function(); + //~^ ERROR: no method named `into_function` found + + let _ = return_with_lifetime_pass.into_function(); + + let _ = return_with_invalid_lifetime.into_function(); + //~^ ERROR: no method named `into_function` found +} diff --git a/crates/bevy_reflect/derive/src/impls/enums.rs b/crates/bevy_reflect/derive/src/impls/enums.rs index 42162717a4..2d479ac2b9 100644 --- a/crates/bevy_reflect/derive/src/impls/enums.rs +++ b/crates/bevy_reflect/derive/src/impls/enums.rs @@ -1,6 +1,6 @@ use crate::derive_data::{EnumVariantFields, ReflectEnum, StructField}; use crate::enum_utility::{EnumVariantOutputData, TryApplyVariantBuilder, VariantBuilder}; -use crate::impls::{impl_type_path, impl_typed}; +use crate::impls::{impl_function_traits, impl_type_path, impl_typed}; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult}; use proc_macro2::{Ident, Span}; use quote::quote; @@ -65,6 +65,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream let type_path_impl = impl_type_path(reflect_enum.meta()); + let function_impls = impl_function_traits(reflect_enum.meta(), &where_clause_options); + let get_type_registration_impl = reflect_enum.get_type_registration(&where_clause_options); let (impl_generics, ty_generics, where_clause) = @@ -79,6 +81,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream #type_path_impl + #function_impls + impl #impl_generics #bevy_reflect_path::Enum for #enum_path #ty_generics #where_reflect_clause { fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match self { diff --git a/crates/bevy_reflect/derive/src/impls/func/from_arg.rs b/crates/bevy_reflect/derive/src/impls/func/from_arg.rs new file mode 100644 index 0000000000..a52f3e2fb4 --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/func/from_arg.rs @@ -0,0 +1,47 @@ +use crate::derive_data::ReflectMeta; +use crate::utility::WhereClauseOptions; +use bevy_macro_utils::fq_std::FQResult; +use quote::quote; + +pub(crate) fn impl_from_arg( + meta: &ReflectMeta, + where_clause_options: &WhereClauseOptions, +) -> proc_macro2::TokenStream { + let bevy_reflect = meta.bevy_reflect_path(); + let type_path = meta.type_path(); + + let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); + + quote! { + impl #impl_generics #bevy_reflect::func::args::FromArg for #type_path #ty_generics #where_reflect_clause { + type Item<'from_arg> = #type_path #ty_generics; + fn from_arg<'from_arg>( + arg: #bevy_reflect::func::args::Arg<'from_arg>, + info: &#bevy_reflect::func::args::ArgInfo, + ) -> #FQResult, #bevy_reflect::func::args::ArgError> { + arg.take_owned(info) + } + } + + impl #impl_generics #bevy_reflect::func::args::FromArg for &'static #type_path #ty_generics #where_reflect_clause { + type Item<'from_arg> = &'from_arg #type_path #ty_generics; + fn from_arg<'from_arg>( + arg: #bevy_reflect::func::args::Arg<'from_arg>, + info: &#bevy_reflect::func::args::ArgInfo, + ) -> #FQResult, #bevy_reflect::func::args::ArgError> { + arg.take_ref(info) + } + } + + impl #impl_generics #bevy_reflect::func::args::FromArg for &'static mut #type_path #ty_generics #where_reflect_clause { + type Item<'from_arg> = &'from_arg mut #type_path #ty_generics; + fn from_arg<'from_arg>( + arg: #bevy_reflect::func::args::Arg<'from_arg>, + info: &#bevy_reflect::func::args::ArgInfo, + ) -> #FQResult, #bevy_reflect::func::args::ArgError> { + arg.take_mut(info) + } + } + } +} diff --git a/crates/bevy_reflect/derive/src/impls/func/function_impls.rs b/crates/bevy_reflect/derive/src/impls/func/function_impls.rs new file mode 100644 index 0000000000..3b42203d41 --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/func/function_impls.rs @@ -0,0 +1,23 @@ +use crate::derive_data::ReflectMeta; +use crate::impls::func::from_arg::impl_from_arg; +use crate::impls::func::get_ownership::impl_get_ownership; +use crate::impls::func::into_return::impl_into_return; +use crate::utility::WhereClauseOptions; +use quote::quote; + +pub(crate) fn impl_function_traits( + meta: &ReflectMeta, + where_clause_options: &WhereClauseOptions, +) -> proc_macro2::TokenStream { + let get_ownership = impl_get_ownership(meta, where_clause_options); + let from_arg = impl_from_arg(meta, where_clause_options); + let into_return = impl_into_return(meta, where_clause_options); + + quote! { + #get_ownership + + #from_arg + + #into_return + } +} diff --git a/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs b/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs new file mode 100644 index 0000000000..65f75c7a64 --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/func/get_ownership.rs @@ -0,0 +1,34 @@ +use crate::derive_data::ReflectMeta; +use crate::utility::WhereClauseOptions; +use quote::quote; + +pub(crate) fn impl_get_ownership( + meta: &ReflectMeta, + where_clause_options: &WhereClauseOptions, +) -> proc_macro2::TokenStream { + let bevy_reflect = meta.bevy_reflect_path(); + let type_path = meta.type_path(); + + let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); + + quote! { + impl #impl_generics #bevy_reflect::func::args::GetOwnership for #type_path #ty_generics #where_reflect_clause { + fn ownership() -> #bevy_reflect::func::args::Ownership { + #bevy_reflect::func::args::Ownership::Owned + } + } + + impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ #type_path #ty_generics #where_reflect_clause { + fn ownership() -> #bevy_reflect::func::args::Ownership { + #bevy_reflect::func::args::Ownership::Ref + } + } + + impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ mut #type_path #ty_generics #where_reflect_clause { + fn ownership() -> #bevy_reflect::func::args::Ownership { + #bevy_reflect::func::args::Ownership::Mut + } + } + } +} diff --git a/crates/bevy_reflect/derive/src/impls/func/into_return.rs b/crates/bevy_reflect/derive/src/impls/func/into_return.rs new file mode 100644 index 0000000000..02c9fcf67a --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/func/into_return.rs @@ -0,0 +1,34 @@ +use crate::derive_data::ReflectMeta; +use crate::utility::WhereClauseOptions; +use quote::quote; + +pub(crate) fn impl_into_return( + meta: &ReflectMeta, + where_clause_options: &WhereClauseOptions, +) -> proc_macro2::TokenStream { + let bevy_reflect = meta.bevy_reflect_path(); + let type_path = meta.type_path(); + + let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); + let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); + + 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> { + #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> { + #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> { + #bevy_reflect::func::Return::Mut(self) + } + } + } +} diff --git a/crates/bevy_reflect/derive/src/impls/func/mod.rs b/crates/bevy_reflect/derive/src/impls/func/mod.rs new file mode 100644 index 0000000000..b092366146 --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/func/mod.rs @@ -0,0 +1,6 @@ +pub(crate) use function_impls::impl_function_traits; + +mod from_arg; +mod function_impls; +mod get_ownership; +mod into_return; diff --git a/crates/bevy_reflect/derive/src/impls/mod.rs b/crates/bevy_reflect/derive/src/impls/mod.rs index db5418b446..276101bf8b 100644 --- a/crates/bevy_reflect/derive/src/impls/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/mod.rs @@ -1,12 +1,13 @@ mod enums; +mod func; mod structs; mod tuple_structs; mod typed; mod values; pub(crate) use enums::impl_enum; +pub(crate) use func::impl_function_traits; pub(crate) use structs::impl_struct; pub(crate) use tuple_structs::impl_tuple_struct; -pub(crate) use typed::impl_type_path; -pub(crate) use typed::impl_typed; +pub(crate) use typed::{impl_type_path, impl_typed}; pub(crate) use values::impl_value; diff --git a/crates/bevy_reflect/derive/src/impls/structs.rs b/crates/bevy_reflect/derive/src/impls/structs.rs index 249ac22745..e9e555f662 100644 --- a/crates/bevy_reflect/derive/src/impls/structs.rs +++ b/crates/bevy_reflect/derive/src/impls/structs.rs @@ -1,4 +1,4 @@ -use crate::impls::{impl_type_path, impl_typed}; +use crate::impls::{impl_function_traits, impl_type_path, impl_typed}; use crate::utility::ident_or_index; use crate::ReflectStruct; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; @@ -54,6 +54,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS let type_path_impl = impl_type_path(reflect_struct.meta()); + let function_impls = impl_function_traits(reflect_struct.meta(), &where_clause_options); + let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options); let (impl_generics, ty_generics, where_clause) = reflect_struct @@ -71,6 +73,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS #type_path_impl + #function_impls + impl #impl_generics #bevy_reflect_path::Struct for #struct_path #ty_generics #where_reflect_clause { fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match name { diff --git a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs index cf43e5fe98..559f63343c 100644 --- a/crates/bevy_reflect/derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/derive/src/impls/tuple_structs.rs @@ -1,4 +1,4 @@ -use crate::impls::{impl_type_path, impl_typed}; +use crate::impls::{impl_function_traits, impl_type_path, impl_typed}; use crate::ReflectStruct; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use quote::{quote, ToTokens}; @@ -46,6 +46,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: let type_path_impl = impl_type_path(reflect_struct.meta()); + let function_impls = impl_function_traits(reflect_struct.meta(), &where_clause_options); + let (impl_generics, ty_generics, where_clause) = reflect_struct .meta() .type_path() @@ -61,6 +63,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: #type_path_impl + #function_impls + impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_path #ty_generics #where_reflect_clause { fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match index { diff --git a/crates/bevy_reflect/derive/src/impls/values.rs b/crates/bevy_reflect/derive/src/impls/values.rs index c0e7b2d4fe..b7eedca212 100644 --- a/crates/bevy_reflect/derive/src/impls/values.rs +++ b/crates/bevy_reflect/derive/src/impls/values.rs @@ -1,4 +1,4 @@ -use crate::impls::{impl_type_path, impl_typed}; +use crate::impls::{impl_function_traits, impl_type_path, impl_typed}; use crate::utility::WhereClauseOptions; use crate::ReflectMeta; use bevy_macro_utils::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult}; @@ -33,6 +33,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { let type_path_impl = impl_type_path(meta); + let function_impls = impl_function_traits(meta, &where_clause_options); + let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); let get_type_registration_impl = meta.get_type_registration(&where_clause_options); @@ -44,6 +46,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { #typed_impl + #function_impls + impl #impl_generics #bevy_reflect_path::Reflect for #type_path #ty_generics #where_reflect_clause { #[inline] fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> { diff --git a/crates/bevy_reflect/derive/src/utility.rs b/crates/bevy_reflect/derive/src/utility.rs index a32fcabeea..a12a0a08d5 100644 --- a/crates/bevy_reflect/derive/src/utility.rs +++ b/crates/bevy_reflect/derive/src/utility.rs @@ -154,13 +154,17 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> { &self, where_clause: Option<&WhereClause>, ) -> proc_macro2::TokenStream { + let type_path = self.meta.type_path(); + let (_, ty_generics, _) = self.meta.type_path().generics().split_for_impl(); + let required_bounds = self.required_bounds(); + // Maintain existing where clause, if any. let mut generic_where_clause = if let Some(where_clause) = where_clause { let predicates = where_clause.predicates.iter(); - quote! {where Self: #required_bounds, #(#predicates,)*} + quote! {where #type_path #ty_generics: #required_bounds, #(#predicates,)*} } else { - quote!(where Self: #required_bounds,) + quote!(where #type_path #ty_generics: #required_bounds,) }; // Add additional reflection trait bounds diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index f5f1158c20..c66b7b3b67 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -1,3 +1,4 @@ +use crate::func::macros::impl_function_traits; use crate::{ self as bevy_reflect, utility::reflect_hasher, ApplyError, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable, @@ -358,6 +359,7 @@ impl Array for DynamicArray { } impl_type_path!((in bevy_reflect) DynamicArray); +impl_function_traits!(DynamicArray); /// An iterator over an [`Array`]. pub struct ArrayIter<'a> { array: &'a dyn Array, diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index 7d0bdccc12..9b3dea9601 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -1,5 +1,6 @@ use bevy_reflect_derive::impl_type_path; +use crate::func::macros::impl_function_traits; use crate::{ self as bevy_reflect, enum_debug, enum_hash, enum_partial_eq, ApplyError, DynamicStruct, DynamicTuple, Enum, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Struct, Tuple, @@ -427,3 +428,4 @@ impl Reflect for DynamicEnum { } impl_type_path!((in bevy_reflect) DynamicEnum); +impl_function_traits!(DynamicEnum); diff --git a/crates/bevy_reflect/src/func/args/arg.rs b/crates/bevy_reflect/src/func/args/arg.rs new file mode 100644 index 0000000000..a79ab592aa --- /dev/null +++ b/crates/bevy_reflect/src/func/args/arg.rs @@ -0,0 +1,80 @@ +use crate::func::args::{ArgError, ArgInfo, Ownership}; +use crate::Reflect; + +/// Represents an argument that can be passed to a [`DynamicFunction`]. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +#[derive(Debug)] +pub enum Arg<'a> { + Owned(Box), + Ref(&'a dyn Reflect), + Mut(&'a mut dyn Reflect), +} + +impl<'a> Arg<'a> { + /// Returns `Ok(T)` if the argument is [`Arg::Owned`]. + pub fn take_owned(self, info: &ArgInfo) -> Result { + match self { + Arg::Owned(arg) => arg.take().map_err(|arg| ArgError::UnexpectedType { + id: info.id().clone(), + expected: ::std::borrow::Cow::Borrowed(info.type_path()), + received: ::std::borrow::Cow::Owned(arg.reflect_type_path().to_string()), + }), + Arg::Ref(_) => Err(ArgError::InvalidOwnership { + id: info.id().clone(), + expected: Ownership::Owned, + received: Ownership::Ref, + }), + Arg::Mut(_) => Err(ArgError::InvalidOwnership { + id: info.id().clone(), + expected: Ownership::Owned, + received: Ownership::Mut, + }), + } + } + + /// Returns `Ok(&T)` if the argument is [`Arg::Ref`]. + pub fn take_ref(self, info: &ArgInfo) -> Result<&'a T, ArgError> { + match self { + Arg::Owned(_) => Err(ArgError::InvalidOwnership { + id: info.id().clone(), + expected: Ownership::Ref, + received: Ownership::Owned, + }), + Arg::Ref(arg) => Ok(arg.downcast_ref().ok_or_else(|| ArgError::UnexpectedType { + id: info.id().clone(), + expected: ::std::borrow::Cow::Borrowed(info.type_path()), + received: ::std::borrow::Cow::Owned(arg.reflect_type_path().to_string()), + })?), + Arg::Mut(_) => Err(ArgError::InvalidOwnership { + id: info.id().clone(), + expected: Ownership::Ref, + received: Ownership::Mut, + }), + } + } + + /// Returns `Ok(&mut T)` if the argument is [`Arg::Mut`]. + pub fn take_mut(self, info: &ArgInfo) -> Result<&'a mut T, ArgError> { + match self { + Arg::Owned(_) => Err(ArgError::InvalidOwnership { + id: info.id().clone(), + expected: Ownership::Mut, + received: Ownership::Owned, + }), + Arg::Ref(_) => Err(ArgError::InvalidOwnership { + id: info.id().clone(), + expected: Ownership::Mut, + received: Ownership::Ref, + }), + Arg::Mut(arg) => { + let received = ::std::borrow::Cow::Owned(arg.reflect_type_path().to_string()); + Ok(arg.downcast_mut().ok_or_else(|| ArgError::UnexpectedType { + id: info.id().clone(), + expected: ::std::borrow::Cow::Borrowed(info.type_path()), + received, + })?) + } + } + } +} diff --git a/crates/bevy_reflect/src/func/args/error.rs b/crates/bevy_reflect/src/func/args/error.rs new file mode 100644 index 0000000000..83794b81d6 --- /dev/null +++ b/crates/bevy_reflect/src/func/args/error.rs @@ -0,0 +1,26 @@ +use alloc::borrow::Cow; + +use thiserror::Error; + +use crate::func::args::{ArgId, Ownership}; + +/// An error that occurs when converting an [argument]. +/// +/// [argument]: crate::func::Arg +#[derive(Debug, Error, PartialEq)] +pub enum ArgError { + /// The argument is not the expected type. + #[error("expected `{expected}` but received `{received}` (@ {id:?})")] + UnexpectedType { + id: ArgId, + expected: Cow<'static, str>, + received: Cow<'static, str>, + }, + /// The argument has the wrong ownership. + #[error("expected {expected} value but received {received} value (@ {id:?})")] + InvalidOwnership { + id: ArgId, + expected: Ownership, + received: Ownership, + }, +} diff --git a/crates/bevy_reflect/src/func/args/from_arg.rs b/crates/bevy_reflect/src/func/args/from_arg.rs new file mode 100644 index 0000000000..7ae61e6856 --- /dev/null +++ b/crates/bevy_reflect/src/func/args/from_arg.rs @@ -0,0 +1,104 @@ +use crate::func::args::{Arg, ArgError, ArgInfo}; + +/// A trait for types that can be created from an [`Arg`]. +/// +/// This trait is used instead of a blanket [`From`] implementation due to coherence issues: +/// we can't implement `From` for both `T` and `&T`/`&mut T`. +/// +/// This trait is automatically implemented when using the `Reflect` [derive macro]. +/// +/// [derive macro]: derive@crate::Reflect +pub trait FromArg { + /// The type of the item created from the argument. + /// + /// This should almost always be the same as `Self`, but with the lifetime `'a`. + type Item<'a>; + + /// Creates an item from an argument. + /// + /// The argument must be of the expected type and ownership. + fn from_arg<'a>(arg: Arg<'a>, info: &ArgInfo) -> Result, ArgError>; +} + +/// Implements the [`FromArg`] trait for the given type. +/// +/// This will implement it for `$ty`, `&$ty`, and `&mut $ty`. +/// +/// See [`impl_function_traits`] for details on syntax. +/// +/// [`impl_function_traits`]: crate::func::macros::impl_function_traits +macro_rules! impl_from_arg { + ( + $ty: ty + $(; + < + $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* + > + )? + $( + [ + $(const $N: ident : $size: ident),* + ] + )? + $( + where + $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),* + )? + ) => { + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::args::FromArg for $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + type Item<'from_arg> = $ty; + fn from_arg<'from_arg>( + arg: $crate::func::args::Arg<'from_arg>, + info: &$crate::func::args::ArgInfo, + ) -> Result, $crate::func::args::ArgError> { + arg.take_owned(info) + } + } + + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::args::FromArg for &'static $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + type Item<'from_arg> = &'from_arg $ty; + fn from_arg<'from_arg>( + arg: $crate::func::args::Arg<'from_arg>, + info: &$crate::func::args::ArgInfo, + ) -> Result, $crate::func::args::ArgError> { + arg.take_ref(info) + } + } + + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::args::FromArg for &'static mut $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + type Item<'from_arg> = &'from_arg mut $ty; + fn from_arg<'from_arg>( + arg: $crate::func::args::Arg<'from_arg>, + info: &$crate::func::args::ArgInfo, + ) -> Result, $crate::func::args::ArgError> { + arg.take_mut(info) + } + } + }; +} + +pub(crate) use impl_from_arg; diff --git a/crates/bevy_reflect/src/func/args/info.rs b/crates/bevy_reflect/src/func/args/info.rs new file mode 100644 index 0000000000..b7330dedb2 --- /dev/null +++ b/crates/bevy_reflect/src/func/args/info.rs @@ -0,0 +1,94 @@ +use alloc::borrow::Cow; + +use crate::func::args::{GetOwnership, Ownership}; +use crate::TypePath; + +/// Type information for an [`Arg`] used in a [`DynamicFunction`]. +/// +/// [`Arg`]: crate::func::args::Arg +/// [`DynamicFunction`]: super::function::DynamicFunction +#[derive(Debug, Clone)] +pub struct ArgInfo { + /// The index of the argument within its function. + index: usize, + /// The name of the argument (if provided). + name: Option>, + /// The ownership of the argument. + ownership: Ownership, + /// The [type path] of the argument. + /// + /// [type path]: TypePath::type_path + type_path: &'static str, +} + +impl ArgInfo { + /// Create a new [`ArgInfo`] with the given argument index and type `T`. + /// + /// To set the name of the argument, use [`Self::with_name`]. + pub fn new(index: usize) -> Self { + Self { + index, + name: None, + ownership: T::ownership(), + type_path: T::type_path(), + } + } + + /// Set the name of the argument. + /// + /// Reflected arguments are not required to have a name and by default are not given one, + /// so this method must be called manually to set the name. + pub fn with_name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + /// The index of the argument within its function. + pub fn index(&self) -> usize { + self.index + } + + /// The name of the argument, if it was given one. + /// + /// Note that this may return `None` even if the argument has a name. + /// 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`. + /// + /// [`DynamicFunctions`]: crate::func::DynamicFunction + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// The ownership of the argument. + pub fn ownership(&self) -> Ownership { + self.ownership + } + + pub fn type_path(&self) -> &'static str { + self.type_path + } + + /// Get an ID representing the argument. + /// + /// This will return `ArgId::Name` if the argument has a name, + /// otherwise `ArgId::Index`. + pub fn id(&self) -> ArgId { + self.name + .clone() + .map(ArgId::Name) + .unwrap_or_else(|| ArgId::Index(self.index)) + } +} + +/// A representation of an argument. +/// +/// This is primarily used for error reporting and debugging. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ArgId { + /// The index of the argument within its function. + Index(usize), + /// The name of the argument. + Name(Cow<'static, str>), +} diff --git a/crates/bevy_reflect/src/func/args/list.rs b/crates/bevy_reflect/src/func/args/list.rs new file mode 100644 index 0000000000..984662f3f5 --- /dev/null +++ b/crates/bevy_reflect/src/func/args/list.rs @@ -0,0 +1,81 @@ +use crate::func::args::Arg; +use crate::Reflect; + +/// A list of arguments that can be passed to a [`DynamicFunction`]. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{Arg, ArgList}; +/// let foo = 123; +/// let bar = 456; +/// let mut baz = 789; +/// let args = ArgList::new() +/// // Push an owned argument +/// .push_owned(foo) +/// // Push an owned and boxed argument +/// .push_boxed(Box::new(foo)) +/// // Push a reference argument +/// .push_ref(&bar) +/// // Push a mutable reference argument +/// .push_mut(&mut baz) +/// // Push a manually constructed argument +/// .push(Arg::Ref(&3.14)); +/// ``` +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +#[derive(Default, Debug)] +pub struct ArgList<'a>(Vec>); + +impl<'a> ArgList<'a> { + /// Create a new empty list of arguments. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Push an [`Arg`] onto the list. + pub fn push(mut self, arg: Arg<'a>) -> Self { + self.0.push(arg); + self + } + + /// Push an [`Arg::Ref`] onto the list with the given reference. + pub fn push_ref(self, arg: &'a dyn Reflect) -> Self { + self.push(Arg::Ref(arg)) + } + + /// Push an [`Arg::Mut`] onto the list with the given mutable reference. + pub fn push_mut(self, arg: &'a mut dyn Reflect) -> Self { + self.push(Arg::Mut(arg)) + } + + /// Push an [`Arg::Owned`] onto the list with the given owned value. + pub fn push_owned(self, arg: impl Reflect) -> Self { + self.push(Arg::Owned(Box::new(arg))) + } + + /// Push an [`Arg::Owned`] onto the list with the given boxed value. + pub fn push_boxed(self, arg: Box) -> Self { + self.push(Arg::Owned(arg)) + } + + /// Pop the last argument from the list, if there is one. + pub fn pop(&mut self) -> Option> { + self.0.pop() + } + + /// Returns the number of arguments in the list. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the list of arguments is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Take ownership of the list of arguments. + pub fn take(self) -> Vec> { + self.0 + } +} diff --git a/crates/bevy_reflect/src/func/args/mod.rs b/crates/bevy_reflect/src/func/args/mod.rs new file mode 100644 index 0000000000..adcbc0ec64 --- /dev/null +++ b/crates/bevy_reflect/src/func/args/mod.rs @@ -0,0 +1,17 @@ +//! Argument types and utilities for working with [`DynamicFunctions`]. +//! +//! [`DynamicFunctions`]: crate::func::DynamicFunction + +pub use arg::*; +pub use error::*; +pub use from_arg::*; +pub use info::*; +pub use list::*; +pub use ownership::*; + +mod arg; +mod error; +mod from_arg; +mod info; +mod list; +mod ownership; diff --git a/crates/bevy_reflect/src/func/args/ownership.rs b/crates/bevy_reflect/src/func/args/ownership.rs new file mode 100644 index 0000000000..554126d295 --- /dev/null +++ b/crates/bevy_reflect/src/func/args/ownership.rs @@ -0,0 +1,103 @@ +use core::fmt::{Display, Formatter}; + +/// A trait for getting the ownership of a type. +/// +/// This trait is automatically implemented when using the `Reflect` [derive macro]. +/// +/// [derive macro]: derive@crate::Reflect +pub trait GetOwnership { + /// Returns the ownership of [`Self`]. + fn ownership() -> Ownership; +} + +/// The ownership of a type. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Ownership { + /// The type is a reference (i.e. `&T`). + Ref, + /// The type is a mutable reference (i.e. `&mut T`). + Mut, + /// The type is owned (i.e. `T`). + Owned, +} + +impl Display for Ownership { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::Ref => write!(f, "reference"), + Self::Mut => write!(f, "mutable reference"), + Self::Owned => write!(f, "owned"), + } + } +} + +/// Implements the [`GetOwnership`] trait for the given type. +/// +/// This will implement it for `$ty`, `&$ty`, and `&mut $ty`. +/// +/// See [`impl_function_traits`] for details on syntax. +/// +/// [`impl_function_traits`]: crate::func::macros::impl_function_traits +macro_rules! impl_get_ownership { + ( + $ty: ty + $(; + < + $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* + > + )? + $( + [ + $(const $N: ident : $size: ident),* + ] + )? + $( + where + $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),* + )? + ) => { + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::args::GetOwnership for $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + fn ownership() -> $crate::func::args::Ownership { + $crate::func::args::Ownership::Owned + } + } + + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::args::GetOwnership for &'_ $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + fn ownership() -> $crate::func::args::Ownership { + $crate::func::args::Ownership::Ref + } + } + + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::args::GetOwnership for &'_ mut $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + fn ownership() -> $crate::func::args::Ownership { + $crate::func::args::Ownership::Mut + } + } + }; +} + +pub(crate) use impl_get_ownership; diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs new file mode 100644 index 0000000000..65290b66d4 --- /dev/null +++ b/crates/bevy_reflect/src/func/error.rs @@ -0,0 +1,15 @@ +use crate::func::args::ArgError; +use thiserror::Error; + +/// An error that occurs when calling a [`DynamicFunction`]. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +#[derive(Debug, Error, PartialEq)] +pub enum FunctionError { + /// An error occurred while converting an argument. + #[error(transparent)] + ArgError(#[from] ArgError), + /// The number of arguments provided does not match the expected number. + #[error("expected {expected} arguments but received {received}")] + InvalidArgCount { expected: usize, received: usize }, +} diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs new file mode 100644 index 0000000000..6ac4b1bbf8 --- /dev/null +++ b/crates/bevy_reflect/src/func/function.rs @@ -0,0 +1,220 @@ +use crate::func::args::{ArgInfo, ArgList}; +use crate::func::error::FunctionError; +use crate::func::info::FunctionInfo; +use crate::func::return_type::Return; +use crate::func::{IntoFunction, ReturnInfo}; +use alloc::borrow::Cow; +use core::fmt::{Debug, Formatter}; +use std::ops::DerefMut; + +/// The result of calling a dynamic [`DynamicFunction`]. +/// +/// Returns `Ok(value)` if the function was called successfully, +/// where `value` is the [`Return`] value of the function. +pub type FunctionResult<'a> = Result, FunctionError>; + +/// A dynamic representation of a Rust function. +/// +/// Internally this stores a function pointer and associated info. +/// +/// You will generally not need to construct this manually. +/// Instead, many functions and closures can be automatically converted using the [`IntoFunction`] trait. +/// +/// # Example +/// +/// Most of the time, a [`DynamicFunction`] can be created using the [`IntoFunction`] trait: +/// +/// ``` +/// # use bevy_reflect::func::args::ArgList; +/// # use bevy_reflect::func::{DynamicFunction, IntoFunction}; +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// // Convert the function into a dynamic function using `IntoFunction::into_function` +/// let mut func: DynamicFunction = add.into_function(); +/// +/// // Dynamically call the function: +/// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); +/// let value = func.call(args).unwrap().unwrap_owned(); +/// +/// // Check the result: +/// assert_eq!(value.downcast_ref::(), Some(&100)); +/// ``` +/// +/// However, in some cases, these functions may need to be created manually: +/// +/// ``` +/// # use bevy_reflect::func::{ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo}; +/// # use bevy_reflect::func::args::ArgInfo; +/// fn append(value: String, list: &mut Vec) -> &mut String { +/// list.push(value); +/// list.last_mut().unwrap() +/// } +/// +/// // Due to the return value being a reference that is not tied to the first argument, +/// // this will fail to compile: +/// // let mut func: DynamicFunction = append.into_function(); +/// +/// // Instead, we need to define the function manually. +/// // We start by defining the shape of the function: +/// let info = FunctionInfo::new() +/// .with_name("append") +/// .with_args(vec![ +/// ArgInfo::new::(0).with_name("value"), +/// ArgInfo::new::<&mut Vec>(1).with_name("list"), +/// ]) +/// .with_return_info( +/// ReturnInfo::new::<&mut String>() +/// ); +/// +/// // Then we define the dynamic function, which will be used to call our `append` function: +/// let mut func = DynamicFunction::new(|mut args, info| { +/// // Arguments are popped from the list in reverse order: +/// let arg1 = args.pop().unwrap().take_mut::>(&info.args()[1]).unwrap(); +/// let arg0 = args.pop().unwrap().take_owned::(&info.args()[0]).unwrap(); +/// +/// // Then we can call our function and return the result: +/// Ok(Return::Mut(append(arg0, arg1))) +/// }, info); +/// +/// let mut list = Vec::::new(); +/// +/// // Dynamically call the function: +/// let args = ArgList::default().push_owned("Hello, World".to_string()).push_mut(&mut list); +/// let value = func.call(args).unwrap().unwrap_mut(); +/// +/// // Mutate the return value: +/// value.downcast_mut::().unwrap().push_str("!!!"); +/// +/// // Check the result: +/// assert_eq!(list, vec!["Hello, World!!!"]); +/// ``` +pub struct DynamicFunction<'env> { + info: FunctionInfo, + func: Box FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>, +} + +impl<'env> DynamicFunction<'env> { + /// Create a new dynamic [`DynamicFunction`]. + /// + /// The given function can be used to call out to a regular function, closure, or method. + /// + /// It's important that the function signature matches the provided [`FunctionInfo`]. + /// This info is used to validate the arguments and return value. + pub fn new FnMut(ArgList<'a>, &FunctionInfo) -> FunctionResult<'a> + 'env>( + func: F, + info: FunctionInfo, + ) -> Self { + Self { + info, + func: Box::new(func), + } + } + + /// Set the name of the function. + /// + /// For [`DynamicFunctions`] created using [`IntoFunction`], + /// the default name will always be the full path to the function as returned by [`std::any::type_name`]. + /// + /// [`DynamicFunctions`]: DynamicFunction + pub fn with_name(mut self, name: impl Into>) -> Self { + self.info = self.info.with_name(name); + self + } + + /// Set the arguments of the function. + /// + /// It is very important that the arguments match the intended function signature, + /// as this is used to validate arguments passed to the function. + pub fn with_args(mut self, args: Vec) -> Self { + self.info = self.info.with_args(args); + self + } + + /// Set the return information of the function. + pub fn with_return_info(mut self, return_info: ReturnInfo) -> Self { + self.info = self.info.with_return_info(return_info); + self + } + + /// Call the function with the given arguments. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{IntoFunction, ArgList}; + /// fn add(left: i32, right: i32) -> i32 { + /// left + right + /// } + /// + /// 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.take::().unwrap(), 100); + /// ``` + pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { + (self.func.deref_mut())(args, &self.info) + } + + /// Call the function with the given arguments and consume the function. + /// + /// This is useful for closures that capture their environment because otherwise + /// any captured variables would still be borrowed by this function. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{IntoFunction, ArgList}; + /// let mut count = 0; + /// let increment = |amount: i32| { + /// count += amount; + /// }; + /// let increment_function = increment.into_function(); + /// let args = ArgList::new().push_owned(5_i32); + /// // We need to drop `increment_function` here so that we + /// // can regain access to `count`. + /// increment_function.call_once(args).unwrap(); + /// assert_eq!(count, 5); + /// ``` + pub fn call_once(mut self, args: ArgList) -> FunctionResult { + (self.func.deref_mut())(args, &self.info) + } + + /// Returns the function info. + pub fn info(&self) -> &FunctionInfo { + &self.info + } +} + +/// Outputs the function signature. +/// +/// 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> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let name = self.info.name().unwrap_or("_"); + write!(f, "DynamicFunction(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> IntoFunction<'env, ()> for DynamicFunction<'env> { + #[inline] + fn into_function(self) -> DynamicFunction<'env> { + self + } +} diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs new file mode 100644 index 0000000000..2e4c985cf2 --- /dev/null +++ b/crates/bevy_reflect/src/func/info.rs @@ -0,0 +1,112 @@ +use crate::func::args::{ArgInfo, GetOwnership, Ownership}; +use crate::TypePath; +use alloc::borrow::Cow; + +/// Type information for a [`DynamicFunction`]. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +#[derive(Debug, Clone)] +pub struct FunctionInfo { + name: Option>, + args: Vec, + return_info: ReturnInfo, +} + +impl FunctionInfo { + /// Create a new [`FunctionInfo`]. + /// + /// To set the name of the function, use [`Self::with_name`]. + pub fn new() -> Self { + Self { + name: None, + args: Vec::new(), + return_info: ReturnInfo::new::<()>(), + } + } + + /// Set the name of the function. + /// + /// Reflected functions are not required to have a name, + /// so this method must be called manually to set the name. + pub fn with_name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + /// Set the arguments of the function. + /// + /// Arguments passed to the function will be validated against the info provided here. + /// Mismatched arguments may result in the function call returning an [error]. + /// + /// [error]: crate::func::FunctionError + pub fn with_args(mut self, args: Vec) -> Self { + self.args = args; + self + } + + /// Set the return information of the function. + pub fn with_return_info(mut self, return_info: ReturnInfo) -> Self { + self.return_info = return_info; + self + } + + /// The name of the function, if it was given one. + /// + /// For [`DynamicFunctions`] created using [`IntoFunction`], + /// the name will always be the full path to the function as returned by [`std::any::type_name`]. + /// + /// [`DynamicFunctions`]: crate::func::DynamicFunction + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// The arguments of the function. + pub fn args(&self) -> &[ArgInfo] { + &self.args + } + + /// The number of arguments the function takes. + pub fn arg_count(&self) -> usize { + self.args.len() + } + + /// The return information of the function. + pub fn return_info(&self) -> &ReturnInfo { + &self.return_info + } +} + +impl Default for FunctionInfo { + fn default() -> Self { + Self::new() + } +} + +/// Information about the return type of a [`DynamicFunction`]. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +#[derive(Debug, Clone)] +pub struct ReturnInfo { + type_path: &'static str, + ownership: Ownership, +} + +impl ReturnInfo { + /// Create a new [`ReturnInfo`] representing the given type, `T`. + pub fn new() -> Self { + Self { + type_path: T::type_path(), + ownership: T::ownership(), + } + } + + /// The type path of the return type. + pub fn type_path(&self) -> &'static str { + self.type_path + } + + /// The ownership of the return type. + pub fn ownership(&self) -> Ownership { + self.ownership + } +} diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs new file mode 100644 index 0000000000..351fc50ab5 --- /dev/null +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -0,0 +1,318 @@ +use crate::func::function::DynamicFunction; +use bevy_utils::all_tuples; + +/// A trait for types that can be converted into a [`DynamicFunction`]. +/// +/// # Blanket Implementation +/// +/// 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. +/// +/// ## Arguments +/// +/// 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> { + /// Converts [`Self`] into a [`DynamicFunction`]. + fn into_function(self) -> DynamicFunction<'env>; +} + +/// Helper macro that returns the number of tokens it receives. +/// +/// This is used to get the argument count. +/// +/// See [here] for details. +/// +/// [here]: https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html#bit-twiddling +macro_rules! count_tts { + () => { 0 }; + ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 }; + ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 }; +} + +/// Helper macro for implementing [`IntoFunction`] on Rust functions. +/// +/// This currently implements it for the following signatures (where `argX` may be any of `T`, `&T`, or `&mut T`): +/// - `fn(arg0, arg1, ..., argN) -> R` +/// - `fn(&Receiver, arg0, arg1, ..., argN) -> &R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &mut R` +/// - `fn(&mut Receiver, arg0, arg1, ..., argN) -> &R` +macro_rules! impl_into_function { + ($(($Arg:ident, $arg:ident)),*) => { + // === Owned Return === // + impl<'env, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn($($Arg),*) -> R> for F + where + $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* + R: $crate::func::IntoReturn + $crate::func::args::GetOwnership + $crate::TypePath, + F: FnMut($($Arg),*) -> R + 'env, + F: for<'a> FnMut($($Arg::Item<'a>),*) -> R + 'env, + { + fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { + const COUNT: usize = count_tts!($($Arg)*); + + let info = $crate::func::FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 0; + vec![ + $($crate::func::args::ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info($crate::func::ReturnInfo::new::()); + + $crate::func::DynamicFunction::new(move |args, _info| { + if args.len() != COUNT { + return Err($crate::func::error::FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [$($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + #[allow(unused_mut)] + let mut _index = 0; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + Ok((self)($($arg,)*).into_return()) + }, info) + } + } + + // === Ref Receiver + Ref Return === // + impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&Receiver, $($Arg),*) -> fn(&R)> for F + where + Receiver: $crate::Reflect + $crate::TypePath, + for<'a> &'a Receiver: $crate::func::args::GetOwnership, + R: $crate::Reflect + $crate::TypePath, + for<'a> &'a R: $crate::func::args::GetOwnership, + $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* + F: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a R + 'env, + F: for<'a> FnMut(&'a Receiver, $($Arg::Item<'a>),*) -> &'a R + 'env, + { + fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + let info = $crate::func::FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + $crate::func::args::ArgInfo::new::<&Receiver>(0), + $($crate::func::args::ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info($crate::func::ReturnInfo::new::<&R>()); + + $crate::func::DynamicFunction::new(move |args, _info| { + if args.len() != COUNT { + return Err($crate::func::error::FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_ref::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + Ok($crate::func::Return::Ref((self)(receiver, $($arg,)*))) + }, info) + } + } + + // === Mut Receiver + Mut Return === // + impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&mut Receiver, $($Arg),*) -> fn(&mut R)> for F + where + Receiver: $crate::Reflect + $crate::TypePath, + for<'a> &'a mut Receiver: $crate::func::args::GetOwnership, + R: $crate::Reflect + $crate::TypePath, + for<'a> &'a mut R: $crate::func::args::GetOwnership, + $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* + F: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut R + 'env, + F: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a mut R + 'env, + { + fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + let info = $crate::func::FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + $crate::func::args::ArgInfo::new::<&mut Receiver>(0), + $($crate::func::args::ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info($crate::func::ReturnInfo::new::<&mut R>()); + + $crate::func::DynamicFunction::new(move |args, _info| { + if args.len() != COUNT { + return Err($crate::func::error::FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + Ok($crate::func::Return::Mut((self)(receiver, $($arg,)*))) + }, info) + } + } + + // === Mut Receiver + Ref Return === // + impl<'env, Receiver, $($Arg,)* R, F> $crate::func::IntoFunction<'env, fn(&mut Receiver, $($Arg),*) -> fn(&mut R) -> &R> for F + where + Receiver: $crate::Reflect + $crate::TypePath, + for<'a> &'a mut Receiver: $crate::func::args::GetOwnership, + R: $crate::Reflect + $crate::TypePath, + for<'a> &'a mut R: $crate::func::args::GetOwnership, + $($Arg: $crate::func::args::FromArg + $crate::func::args::GetOwnership + $crate::TypePath,)* + F: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a R + 'env, + F: for<'a> FnMut(&'a mut Receiver, $($Arg::Item<'a>),*) -> &'a R + 'env, + { + fn into_function(mut self) -> $crate::func::DynamicFunction<'env> { + const COUNT: usize = count_tts!(Receiver $($Arg)*); + + let info = $crate::func::FunctionInfo::new() + .with_name(std::any::type_name::()) + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + $crate::func::args::ArgInfo::new::<&mut Receiver>(0), + $($crate::func::args::ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info($crate::func::ReturnInfo::new::<&mut R>()); + + $crate::func::DynamicFunction::new(move |args, _info| { + if args.len() != COUNT { + return Err($crate::func::error::FunctionError::InvalidArgCount { + expected: COUNT, + received: args.len(), + }); + } + + let [receiver, $($arg,)*] = args.take().try_into().expect("invalid number of arguments"); + + let receiver = receiver.take_mut::(_info.args().get(0).expect("argument index out of bounds"))?; + + #[allow(unused_mut)] + let mut _index = 1; + let ($($arg,)*) = ($($Arg::from_arg($arg, { + _index += 1; + _info.args().get(_index - 1).expect("argument index out of bounds") + })?,)*); + Ok($crate::func::Return::Ref((self)(receiver, $($arg,)*))) + }, info) + } + } + }; +} + +all_tuples!(impl_into_function, 0, 15, Arg, arg); diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs new file mode 100644 index 0000000000..a6be3850e3 --- /dev/null +++ b/crates/bevy_reflect/src/func/macros.rs @@ -0,0 +1,99 @@ +/// Helper macro to implement the necessary traits for function reflection. +/// +/// This macro calls the following macros: +/// - [`impl_get_ownership`](crate::func::args::impl_get_ownership) +/// - [`impl_from_arg`](crate::func::args::impl_from_arg) +/// - [`impl_into_return`](crate::func::impl_into_return) +/// +/// # Syntax +/// +/// For non-generic types, the macro simply expects the type: +/// +/// ```ignore +/// impl_function_traits!(foo::bar::Baz); +/// ``` +/// +/// For generic types, however, the generic type parameters must also be given in angle brackets (`<` and `>`): +/// +/// ```ignore +/// impl_function_traits!(foo::bar::Baz; ); +/// ``` +/// +/// For generic const parameters, they must be given in square brackets (`[` and `]`): +/// +/// ```ignore +/// impl_function_traits!(foo::bar::Baz; [const N: usize]); +/// ``` +macro_rules! impl_function_traits { + ( + $ty: ty + $(; + < + $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* + > + )? + $( + [ + $(const $N: ident : $size: ident),* + ] + )? + $( + where + $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),* + )? + ) => { + $crate::func::args::impl_get_ownership!( + $ty + $(; + < + $($T $(: $T1 $(+ $T2)*)?),* + > + )? + $( + [ + $(const $N : $size),* + ] + )? + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + ); + $crate::func::args::impl_from_arg!( + $ty + $(; + < + $($T $(: $T1 $(+ $T2)*)?),* + > + )? + $( + [ + $(const $N : $size),* + ] + )? + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + ); + $crate::func::impl_into_return!( + $ty + $(; + < + $($T $(: $T1 $(+ $T2)*)?),* + > + )? + $( + [ + $(const $N : $size),* + ] + )? + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + ); + }; +} + +pub(crate) use impl_function_traits; diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs new file mode 100644 index 0000000000..a964cf60f7 --- /dev/null +++ b/crates/bevy_reflect/src/func/mod.rs @@ -0,0 +1,265 @@ +//! Reflection-based dynamic functions. +//! +//! This module provides a way to pass around and call functions dynamically +//! using the [`DynamicFunction`] type. +//! +//! Many simple functions and closures can be automatically converted to [`DynamicFunction`] +//! using the [`IntoFunction`] trait. +//! +//! Once the [`DynamicFunction`] 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 +//! +//! ``` +//! # use bevy_reflect::Reflect; +//! # use bevy_reflect::func::args::ArgList; +//! # use bevy_reflect::func::{DynamicFunction, FunctionResult, IntoFunction, Return}; +//! fn add(a: i32, b: i32) -> i32 { +//! a + b +//! } +//! +//! let mut func: DynamicFunction = add.into_function(); +//! let args: ArgList = ArgList::default() +//! // Pushing a known type with owned ownership +//! .push_owned(25_i32) +//! // Pushing a reflected type with owned ownership +//! .push_boxed(Box::new(75_i32) as Box); +//! let result: FunctionResult = func.call(args); +//! let value: Return = result.unwrap(); +//! assert_eq!(value.unwrap_owned().downcast_ref::(), Some(&100)); +//! ``` +//! +//! [`Reflect`]: crate::Reflect + +pub use error::*; +pub use function::*; +pub use info::*; +pub use into_function::*; +pub use return_type::*; + +pub use args::{Arg, ArgError, ArgList}; + +pub mod args; +mod error; +mod function; +mod info; +mod into_function; +pub(crate) mod macros; +mod return_type; + +#[cfg(test)] +mod tests { + use super::*; + use crate as bevy_reflect; + use crate::func::args::{ArgError, ArgId, ArgList, Ownership}; + use crate::{Reflect, TypePath}; + use alloc::borrow::Cow; + + #[test] + fn should_create_dynamic_function() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let 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::(), 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::(), 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::(), 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::(), + 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::(), 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::(), 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")); + } + + #[test] + fn should_error_on_missing_args() { + fn foo(_: i32) {} + + let mut func = foo.into_function(); + let args = ArgList::new(); + let result = func.call(args); + assert_eq!( + result.unwrap_err(), + FunctionError::InvalidArgCount { + expected: 1, + received: 0 + } + ); + } + + #[test] + fn should_error_on_too_many_args() { + fn foo() {} + + let mut func = foo.into_function(); + let args = ArgList::new().push_owned(123_i32); + let result = func.call(args); + assert_eq!( + result.unwrap_err(), + FunctionError::InvalidArgCount { + expected: 0, + received: 1 + } + ); + } + + #[test] + fn should_error_on_invalid_arg_type() { + fn foo(_: i32) {} + + let mut func = foo.into_function(); + let args = ArgList::new().push_owned(123_u32); + let result = func.call(args); + assert_eq!( + result.unwrap_err(), + FunctionError::ArgError(ArgError::UnexpectedType { + id: ArgId::Index(0), + expected: Cow::Borrowed(i32::type_path()), + received: Cow::Borrowed(u32::type_path()) + }) + ); + } + + #[test] + fn should_error_on_invalid_arg_ownership() { + fn foo(_: &i32) {} + + let mut func = foo.into_function(); + let args = ArgList::new().push_owned(123_i32); + let result = func.call(args); + assert_eq!( + result.unwrap_err(), + FunctionError::ArgError(ArgError::InvalidOwnership { + id: ArgId::Index(0), + expected: Ownership::Ref, + received: Ownership::Owned + }) + ); + } + + #[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); + } +} diff --git a/crates/bevy_reflect/src/func/return_type.rs b/crates/bevy_reflect/src/func/return_type.rs new file mode 100644 index 0000000000..b9e94dca63 --- /dev/null +++ b/crates/bevy_reflect/src/func/return_type.rs @@ -0,0 +1,149 @@ +use crate::Reflect; + +/// The return type of a [`DynamicFunction`]. +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +#[derive(Debug)] +pub enum Return<'a> { + /// The function returns nothing (i.e. it returns `()`). + Unit, + /// The function returns an owned value. + Owned(Box), + /// The function returns a reference to a value. + Ref(&'a dyn Reflect), + /// The function returns a mutable reference to a value. + Mut(&'a mut dyn Reflect), +} + +impl<'a> Return<'a> { + /// Returns `true` if the return value is [`Self::Unit`]. + pub fn is_unit(&self) -> bool { + matches!(self, Return::Unit) + } + + /// Unwraps the return value as an owned value. + /// + /// # Panics + /// + /// Panics if the return value is not [`Self::Owned`]. + pub fn unwrap_owned(self) -> Box { + match self { + Return::Owned(value) => value, + _ => panic!("expected owned value"), + } + } + + /// Unwraps the return value as a reference to a value. + /// + /// # Panics + /// + /// Panics if the return value is not [`Self::Ref`]. + pub fn unwrap_ref(self) -> &'a dyn Reflect { + match self { + Return::Ref(value) => value, + _ => panic!("expected reference value"), + } + } + + /// Unwraps the return value as a mutable reference to a value. + /// + /// # Panics + /// + /// Panics if the return value is not [`Self::Mut`]. + pub fn unwrap_mut(self) -> &'a mut dyn Reflect { + match self { + Return::Mut(value) => value, + _ => panic!("expected mutable reference value"), + } + } +} + +/// A trait for types that can be converted into a [`Return`] value. +/// +/// This trait is used instead of a blanket [`Into`] implementation due to coherence issues: +/// we can't implement `Into` for both `T` and `&T`/`&mut T`. +/// +/// This trait is automatically implemented when using the `Reflect` [derive macro]. +/// +/// [derive macro]: derive@crate::Reflect +pub trait IntoReturn { + /// Converts [`Self`] into a [`Return`] value. + fn into_return<'a>(self) -> Return<'a>; +} + +impl IntoReturn for () { + fn into_return<'a>(self) -> Return<'a> { + Return::Unit + } +} + +/// Implements the [`IntoReturn`] trait for the given type. +/// +/// This will implement it for `ty`, `&ty`, and `&mut ty`. +/// +/// See [`impl_function_traits`] for details on syntax. +/// +/// [`impl_function_traits`]: crate::func::macros::impl_function_traits +macro_rules! impl_into_return { + ( + $ty: ty + $(; + < + $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* + > + )? + $( + [ + $(const $N: ident : $size: ident),* + ] + )? + $( + where + $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),* + )? + ) => { + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::IntoReturn for $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> { + $crate::func::Return::Owned(Box::new(self)) + } + } + + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::IntoReturn for &'static $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> { + $crate::func::Return::Ref(self) + } + } + + impl < + $($($T $(: $T1 $(+ $T2)*)?),*)? + $(, $(const $N : $size),*)? + > $crate::func::IntoReturn for &'static mut $ty + $( + where + $($U $(: $U1 $(+ $U2)*)?),* + )? + { + fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> { + $crate::func::Return::Mut(self) + } + } + }; +} + +pub(crate) use impl_into_return; diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 3943ac269b..00e0cc9544 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -1,8 +1,9 @@ use bevy_reflect_derive::impl_type_path; -use smallvec::SmallVec; +use smallvec::{Array as SmallArray, SmallVec}; use std::any::Any; +use crate::func::macros::impl_function_traits; use crate::utility::GenericTypeInfoCell; use crate::{ self as bevy_reflect, ApplyError, FromReflect, FromType, GetTypeRegistration, List, ListInfo, @@ -10,7 +11,7 @@ use crate::{ TypePath, TypeRegistration, Typed, }; -impl List for SmallVec +impl List for SmallVec where T::Item: FromReflect + TypePath, { @@ -32,7 +33,7 @@ where fn insert(&mut self, index: usize, value: Box) { let value = value.take::().unwrap_or_else(|value| { - ::Item::from_reflect(&*value).unwrap_or_else(|| { + ::Item::from_reflect(&*value).unwrap_or_else(|| { panic!( "Attempted to insert invalid value of type {}.", value.reflect_type_path() @@ -48,7 +49,7 @@ where fn push(&mut self, value: Box) { let value = value.take::().unwrap_or_else(|value| { - ::Item::from_reflect(&*value).unwrap_or_else(|| { + ::Item::from_reflect(&*value).unwrap_or_else(|| { panic!( "Attempted to push invalid value of type {}.", value.reflect_type_path() @@ -77,7 +78,7 @@ where } } -impl Reflect for SmallVec +impl Reflect for SmallVec where T::Item: FromReflect + TypePath, { @@ -147,7 +148,7 @@ where } } -impl Typed for SmallVec +impl Typed for SmallVec where T::Item: FromReflect + TypePath, { @@ -157,9 +158,9 @@ where } } -impl_type_path!(::smallvec::SmallVec); +impl_type_path!(::smallvec::SmallVec); -impl FromReflect for SmallVec +impl FromReflect for SmallVec where T::Item: FromReflect + TypePath, { @@ -167,7 +168,7 @@ where if let ReflectRef::List(ref_list) = reflect.reflect_ref() { let mut new_list = Self::with_capacity(ref_list.len()); for field in ref_list.iter() { - new_list.push(::Item::from_reflect(field)?); + new_list.push(::Item::from_reflect(field)?); } Some(new_list) } else { @@ -176,7 +177,7 @@ where } } -impl GetTypeRegistration for SmallVec +impl GetTypeRegistration for SmallVec where T::Item: FromReflect + TypePath, { @@ -186,3 +187,5 @@ where registration } } + +impl_function_traits!(SmallVec; where T::Item: FromReflect + TypePath); diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index bcee1b51ee..4c1a828405 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,3 +1,4 @@ +use crate::func::macros::impl_function_traits; use crate::std_traits::ReflectDefault; use crate::utility::{ reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell, @@ -397,6 +398,8 @@ impl_reflect_for_veclike!( Vec::pop, [T] ); +impl_function_traits!(Vec; ); + impl_reflect_for_veclike!( ::alloc::collections::VecDeque, VecDeque::insert, @@ -405,6 +408,7 @@ impl_reflect_for_veclike!( VecDeque::pop_back, VecDeque:: ); +impl_function_traits!(VecDeque; ); macro_rules! impl_reflect_for_hashmap { ($ty:path) => { @@ -634,10 +638,24 @@ macro_rules! impl_reflect_for_hashmap { impl_reflect_for_hashmap!(::std::collections::HashMap); impl_type_path!(::std::collections::hash_map::RandomState); impl_type_path!(::std::collections::HashMap); +impl_function_traits!(::std::collections::HashMap; + < + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); impl_reflect_for_hashmap!(bevy_utils::hashbrown::HashMap); impl_type_path!(::bevy_utils::hashbrown::hash_map::DefaultHashBuilder); impl_type_path!(::bevy_utils::hashbrown::HashMap); +impl_function_traits!(::bevy_utils::hashbrown::HashMap; + < + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); impl Map for ::std::collections::BTreeMap where @@ -851,6 +869,12 @@ where } impl_type_path!(::std::collections::BTreeMap); +impl_function_traits!(::std::collections::BTreeMap; + < + K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + TypePath + GetTypeRegistration + > +); impl Array for [T; N] { #[inline] @@ -1011,6 +1035,8 @@ impl GetTypeRegistr } } +impl_function_traits!([T; N]; [const N: usize]); + impl_reflect! { #[type_path = "core::option"] enum Option { @@ -1168,6 +1194,8 @@ impl FromReflect for Cow<'static, str> { } } +impl_function_traits!(Cow<'static, str>); + impl TypePath for [T] where [T]: ToOwned, @@ -1346,6 +1374,8 @@ impl FromReflect for Co } } +impl_function_traits!(Cow<'static, [T]>; ); + impl Reflect for &'static str { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -1452,6 +1482,8 @@ impl FromReflect for &'static str { } } +impl_function_traits!(&'static str); + impl Reflect for &'static Path { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -1557,6 +1589,8 @@ impl FromReflect for &'static Path { } } +impl_function_traits!(&'static Path); + impl Reflect for Cow<'static, Path> { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -1672,6 +1706,8 @@ impl GetTypeRegistration for Cow<'static, Path> { } } +impl_function_traits!(Cow<'static, Path>); + #[cfg(test)] mod tests { use crate as bevy_reflect; diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 2d84fc63c7..dcd77bb214 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -476,6 +476,7 @@ mod array; mod fields; mod from_reflect; +pub mod func; mod list; mod map; mod path; diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 5b786ee76a..8b27eadefb 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher}; use bevy_reflect_derive::impl_type_path; +use crate::func::macros::impl_function_traits; use crate::utility::reflect_hasher; use crate::{ self as bevy_reflect, ApplyError, FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, @@ -369,6 +370,7 @@ impl Reflect for DynamicList { } impl_type_path!((in bevy_reflect) DynamicList); +impl_function_traits!(DynamicList); impl Debug for DynamicList { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index b50c6975b5..844be9a247 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -4,6 +4,7 @@ use std::fmt::{Debug, Formatter}; use bevy_reflect_derive::impl_type_path; use bevy_utils::{Entry, HashMap}; +use crate::func::macros::impl_function_traits; use crate::{ self as bevy_reflect, ApplyError, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable, @@ -417,6 +418,7 @@ impl Reflect for DynamicMap { } impl_type_path!((in bevy_reflect) DynamicMap); +impl_function_traits!(DynamicMap); impl Debug for DynamicMap { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 4585a4382f..e088fe3219 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,4 +1,5 @@ use crate::attributes::{impl_custom_attribute_methods, CustomAttributes}; +use crate::func::macros::impl_function_traits; use crate::{ self as bevy_reflect, ApplyError, NamedField, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable, @@ -499,6 +500,7 @@ impl Reflect for DynamicStruct { } impl_type_path!((in bevy_reflect) DynamicStruct); +impl_function_traits!(DynamicStruct); impl Debug for DynamicStruct { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index cf111edcdf..4e96d50f20 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -710,6 +710,31 @@ macro_rules! impl_type_path_tuple { all_tuples!(impl_type_path_tuple, 0, 12, P); +macro_rules! impl_get_ownership_tuple { + ($($name: ident),*) => { + $crate::func::args::impl_get_ownership!(($($name,)*); <$($name),*>); + }; +} + +all_tuples!(impl_get_ownership_tuple, 0, 12, P); + +macro_rules! impl_from_arg_tuple { + ($($name: ident),*) => { + $crate::func::args::impl_from_arg!(($($name,)*); <$($name: FromReflect + TypePath + GetTypeRegistration),*>); + }; +} + +all_tuples!(impl_from_arg_tuple, 0, 12, P); + +macro_rules! impl_into_return_tuple { + ($($name: ident),+) => { + $crate::func::impl_into_return!(($($name,)*); <$($name: FromReflect + TypePath + GetTypeRegistration),*>); + }; +} + +// The unit type (i.e. `()`) is special-cased, so we skip implementing it here. +all_tuples!(impl_into_return_tuple, 1, 12, P); + #[cfg(test)] mod tests { use super::Tuple; diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index 56767cd0e1..ac3a4d4877 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -1,6 +1,7 @@ use bevy_reflect_derive::impl_type_path; use crate::attributes::{impl_custom_attribute_methods, CustomAttributes}; +use crate::func::macros::impl_function_traits; use crate::{ self as bevy_reflect, ApplyError, DynamicTuple, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Tuple, TypeInfo, TypePath, TypePathTable, UnnamedField, @@ -408,6 +409,7 @@ impl Reflect for DynamicTupleStruct { } impl_type_path!((in bevy_reflect) DynamicTupleStruct); +impl_function_traits!(DynamicTupleStruct); impl Debug for DynamicTupleStruct { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/examples/README.md b/examples/README.md index c0a4a76780..7681782792 100644 --- a/examples/README.md +++ b/examples/README.md @@ -343,6 +343,7 @@ Example | Description --- | --- [Custom Attributes](../examples/reflection/custom_attributes.rs) | Registering and accessing custom attributes on reflected types [Dynamic Types](../examples/reflection/dynamic_types.rs) | How dynamic types are used with reflection +[Function Reflection](../examples/reflection/function_reflection.rs) | Demonstrates how functions can be called dynamically using reflection [Generic Reflection](../examples/reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection [Reflection](../examples/reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types [Reflection Types](../examples/reflection/reflection_types.rs) | Illustrates the various reflection types available diff --git a/examples/reflection/function_reflection.rs b/examples/reflection/function_reflection.rs new file mode 100644 index 0000000000..8f02cec457 --- /dev/null +++ b/examples/reflection/function_reflection.rs @@ -0,0 +1,157 @@ +//! This example demonstrates how functions can be called dynamically using reflection. +//! +//! Function reflection is useful for calling regular Rust functions in a dynamic context, +//! where the types of arguments, return values, and even the function itself aren't known at compile time. +//! +//! This can be used for things like adding scripting support to your application, +//! processing deserialized reflection data, or even just storing type-erased versions of your functions. + +use bevy::reflect::func::args::ArgInfo; +use bevy::reflect::func::{ + ArgList, DynamicFunction, FunctionInfo, IntoFunction, Return, ReturnInfo, +}; +use bevy::reflect::Reflect; + +// Note that the `dbg!` invocations are used purely for demonstration purposes +// and are not strictly necessary for the example to work. +fn main() { + // There are times when it may be helpful to store a function away for later. + // In Rust, we can do this by storing either a function pointer or a function trait object. + // For example, say we wanted to store the following function: + fn add(left: i32, right: i32) -> i32 { + left + right + } + + // We could store it as either of the following: + let fn_pointer: fn(i32, i32) -> i32 = add; + let fn_trait_object: Box i32> = Box::new(add); + + // And we can call them like so: + let result = fn_pointer(2, 2); + assert_eq!(result, 4); + let result = fn_trait_object(2, 2); + assert_eq!(result, 4); + + // However, you'll notice that we have to know the types of the arguments and return value at compile time. + // This means there's not really a way to store or call these functions dynamically at runtime. + // 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()); + + // 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. + // Now we can generate a list of arguments: + let args: ArgList = dbg!(ArgList::new().push_owned(2_i32).push_owned(2_i32)); + + // And finally, we can call the function. + // This returns a `Result` indicating whether the function was called successfully. + // For now, we'll just unwrap it to get our `Return` value, + // which is an enum containing the function's return value. + let return_value: Return = dbg!(function.call(args).unwrap()); + + // The `Return` value can be pattern matched or unwrapped to get the underlying reflection data. + // For the sake of brevity, we'll just unwrap it here and downcast it to the expected type of `i32`. + let value: Box = return_value.unwrap_owned(); + assert_eq!(value.take::().unwrap(), 4); + + // The same can also be done for closures. + let mut count = 0; + let increment = |amount: i32| { + count += amount; + }; + let increment_function: DynamicFunction = dbg!(increment.into_function()); + 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()); + 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. + // 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)] + struct Data { + value: String, + } + + impl Data { + fn set_value(&mut self, value: String) { + self.value = value; + } + + // Note that only `&'static str` implements `Reflect`. + // To get around this limitation we can use `&String` instead. + fn get_value(&self) -> &String { + &self.value + } + } + + let mut data = Data::default(); + + let mut 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 args = dbg!(ArgList::new().push_ref(&data)); + let return_value = dbg!(get_value.call(args).unwrap()); + let value: &dyn Reflect = return_value.unwrap_ref(); + assert_eq!(value.downcast_ref::().unwrap(), "Hello, world!"); + + // Lastly, for more complex use cases, you can always create a custom `DynamicFunction` manually. + // This is useful for functions that can't be converted via the `IntoFunction` trait. + // For example, this function doesn't implement `IntoFunction` due to the fact that + // the lifetime of the return value is not tied to the lifetime of the first argument. + fn get_or_insert(value: i32, container: &mut Option) -> &i32 { + if container.is_none() { + *container = Some(value); + } + + container.as_ref().unwrap() + } + + let mut get_or_insert_function = dbg!(DynamicFunction::new( + |mut args, info| { + let container_info = &info.args()[1]; + let value_info = &info.args()[0]; + + // The `ArgList` contains the arguments in the order they were pushed. + // Therefore, we need to pop them in reverse order. + let container = args + .pop() + .unwrap() + .take_mut::>(container_info) + .unwrap(); + let value = args.pop().unwrap().take_owned::(value_info).unwrap(); + + Ok(Return::Ref(get_or_insert(value, container))) + }, + FunctionInfo::new() + // We can optionally provide a name for the function + .with_name("get_or_insert") + // Since our function takes arguments, we MUST provide that argument information. + // The arguments should be provided in the order they are defined in the function. + // This is used to validate any arguments given at runtime. + .with_args(vec![ + ArgInfo::new::(0).with_name("value"), + ArgInfo::new::<&mut Option>(1).with_name("container"), + ]) + // We can optionally provide return information as well. + .with_return_info(ReturnInfo::new::<&i32>()), + )); + + let mut container: Option = None; + + let args = dbg!(ArgList::new().push_owned(5_i32).push_mut(&mut container)); + let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref(); + assert_eq!(value.downcast_ref::(), Some(&5)); + + let args = dbg!(ArgList::new().push_owned(500_i32).push_mut(&mut container)); + let value = dbg!(get_or_insert_function.call(args).unwrap()).unwrap_ref(); + assert_eq!(value.downcast_ref::(), Some(&5)); +}