From d842d69be25954ff4d15f17b8bb1c5a2afcc2d89 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Mon, 9 Dec 2024 22:09:03 -0700 Subject: [PATCH 1/4] Add reflect_fn proc macro --- .../bevy_reflect/derive/src/impls/func/mod.rs | 2 + .../derive/src/impls/func/reflect_fn.rs | 167 ++++++++++++++++++ crates/bevy_reflect/derive/src/impls/mod.rs | 2 +- crates/bevy_reflect/derive/src/lib.rs | 6 + crates/bevy_reflect/src/func/mod.rs | 76 ++++++++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs diff --git a/crates/bevy_reflect/derive/src/impls/func/mod.rs b/crates/bevy_reflect/derive/src/impls/func/mod.rs index b092366146..a3e6e9aa64 100644 --- a/crates/bevy_reflect/derive/src/impls/func/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/func/mod.rs @@ -1,6 +1,8 @@ pub(crate) use function_impls::impl_function_traits; +pub(crate) use reflect_fn::reflect_fn; mod from_arg; mod function_impls; mod get_ownership; mod into_return; +mod reflect_fn; diff --git a/crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs b/crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs new file mode 100644 index 0000000000..d3f1f31284 --- /dev/null +++ b/crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs @@ -0,0 +1,167 @@ +use bevy_macro_utils::fq_std::FQResult; +use proc_macro2::Ident; +use quote::{format_ident, quote}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{parenthesized, parse_macro_input, token, Block, ExprBlock, Type}; +use syn::{LitStr, ReturnType, Token}; + +struct Arg { + mutability: Option, + name: Ident, + _colon: Token![:], + ty: Type, +} + +impl Parse for Arg { + fn parse(input: ParseStream) -> syn::Result { + let mutability = if input.peek(Token![mut]) { + Some(input.parse()?) + } else { + None + }; + + Ok(Self { + mutability, + name: input.parse()?, + _colon: input.parse()?, + ty: input.parse()?, + }) + } +} + +enum FnName { + #[expect( + dead_code, + reason = "for documenting via the type system what `Anon` expects to parse" + )] + Anon(Token![_]), + Lit(LitStr), + Name(Ident), + Expr(ExprBlock), +} + +impl Parse for FnName { + fn parse(input: ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![_]) { + Ok(Self::Anon(input.parse()?)) + } else if lookahead.peek(LitStr) { + Ok(Self::Lit(input.parse()?)) + } else if lookahead.peek(syn::Ident) { + Ok(Self::Name(input.parse()?)) + } else if lookahead.peek(token::Brace) { + Ok(Self::Expr(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +struct ReflectFn { + mutability: Option, + movability: Option, + _fn_token: Token![fn], + name: FnName, + _parens: token::Paren, + args: Punctuated, + return_type: ReturnType, + body: Block, +} + +impl Parse for ReflectFn { + fn parse(input: ParseStream) -> syn::Result { + let mutability = if input.peek(Token![mut]) { + Some(input.parse()?) + } else { + None + }; + let movability = if input.peek(Token![move]) { + Some(input.parse()?) + } else { + None + }; + + let content; + Ok(Self { + mutability, + movability, + _fn_token: input.parse()?, + name: input.parse()?, + _parens: parenthesized!(content in input), + args: content.parse_terminated(Arg::parse, Token![,])?, + return_type: input.parse()?, + body: input.parse()?, + }) + } +} + +pub(crate) fn reflect_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as ReflectFn); + + let bevy_reflect_path = crate::meta::get_bevy_reflect_path(); + + let arg_list = format_ident!("args"); + + let dynamic_function = if input.mutability.is_some() { + quote! { #bevy_reflect_path::func::DynamicFunctionMut } + } else { + quote! { #bevy_reflect_path::func::DynamicFunction } + }; + + let movability = &input.movability; + + let extract_args = input.args.iter().map(|arg| { + let mutability = &arg.mutability; + let name = &arg.name; + let ty = &arg.ty; + + quote! { let #mutability #name = #arg_list.take::<#ty>()?; } + }); + + let body = &input.body; + + let info = match &input.name { + FnName::Anon(_) => quote! { #bevy_reflect_path::func::SignatureInfo::anonymous() }, + FnName::Lit(name) => { + quote! { #bevy_reflect_path::func::SignatureInfo::named(#name) } + } + FnName::Name(name) => { + let name = name.to_string(); + quote! { #bevy_reflect_path::func::SignatureInfo::named(#name) } + } + FnName::Expr(expr) => { + quote! { #bevy_reflect_path::func::SignatureInfo::named(#expr) } + } + }; + + let arg_info = input.args.iter().enumerate().map(|(index, arg)| { + let name = &arg.name; + let ty = &arg.ty; + + quote! { + #bevy_reflect_path::func::args::ArgInfo::new::<#ty>(#index).with_name(stringify!(#name)) + } + }); + + let return_ty = match input.return_type { + ReturnType::Default => quote! { () }, + ReturnType::Type(_, ty) => quote! { #ty }, + }; + + proc_macro::TokenStream::from(quote! {{ + #dynamic_function::new( + #[allow(unused_mut)] + #movability |mut #arg_list| { + #(#extract_args)* + #FQResult::Ok(#bevy_reflect_path::func::IntoReturn::into_return(#body)) + }, + #[allow(unused_braces)] + #bevy_reflect_path::func::FunctionInfo::new( + #info + .with_args(::alloc::vec![#(#arg_info),*]) + .with_return::<#return_ty>() + ), + ) + }}) +} diff --git a/crates/bevy_reflect/derive/src/impls/mod.rs b/crates/bevy_reflect/derive/src/impls/mod.rs index 6477c4041e..f1ea786858 100644 --- a/crates/bevy_reflect/derive/src/impls/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/mod.rs @@ -2,7 +2,7 @@ mod assertions; mod common; mod enums; #[cfg(feature = "functions")] -mod func; +pub(crate) mod func; mod opaque; mod structs; mod tuple_structs; diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 276371427b..9b02bc3f16 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -826,3 +826,9 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream { }; }) } + +#[cfg(feature = "functions")] +#[proc_macro] +pub fn reflect_fn(input: TokenStream) -> TokenStream { + impls::func::reflect_fn(input) +} diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index f990e135f0..e27a435e1b 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -189,6 +189,7 @@ pub mod signature; #[cfg(test)] mod tests { + use crate as bevy_reflect; use alloc::borrow::Cow; use super::*; @@ -197,6 +198,7 @@ mod tests { func::args::{ArgError, ArgList, Ownership}, TypePath, }; + use bevy_reflect_derive::reflect_fn; #[test] fn should_error_on_missing_args() { @@ -263,4 +265,78 @@ mod tests { }) ); } + + #[test] + fn should_create_dynamic_function_with_macro() { + let func = reflect_fn!( + fn add(a: i32, b: i32) -> i32 { + a + b + } + ); + + let info = func.info(); + + assert_eq!(info.name().unwrap(), "add"); + assert_eq!(info.base().arg_count(), 2); + assert_eq!(info.base().args()[0].name(), Some("a")); + assert!(info.base().args()[0].is::()); + assert_eq!(info.base().args()[1].name(), Some("b")); + assert!(info.base().args()[1].is::()); + assert!(info.base().return_info().is::()); + + let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_downcast_ref::(), Some(&100)); + } + + #[test] + fn should_create_zero_arg_dynamic_function_with_macro() { + let func = reflect_fn!( + fn shout() { + println!("HEY!!!"); + } + ); + + let info = func.info(); + + assert_eq!(info.name().unwrap(), "shout"); + assert_eq!(info.base().arg_count(), 0); + assert!(info.base().return_info().is::<()>()); + } + + #[test] + fn should_create_anonymous_dynamic_function_with_macro() { + let func = reflect_fn!( + fn _(a: i32, b: i32) -> i32 { + a + b + } + ); + + let info = func.info(); + assert_eq!(info.name(), None); + } + + #[test] + fn should_create_string_named_dynamic_function_with_macro() { + let func = reflect_fn!( + fn "some cool function"(a: i32, b: i32) -> i32 { + a + b + } + ); + + let info = func.info(); + assert_eq!(info.name().unwrap(), "some cool function"); + } + + #[test] + fn should_create_expression_named_dynamic_function_with_macro() { + let func = reflect_fn!( + fn {concat!("some", " cool ", "function")}(a: i32, b: i32) -> i32 { + a + b + } + ); + + let info = func.info(); + assert_eq!(info.name().unwrap(), "some cool function"); + } } From aed6fdc4f7fae6856ae04d275666105cc281a2b2 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Tue, 10 Dec 2024 12:52:57 -0700 Subject: [PATCH 2/4] Switch to macro_rules! --- .../bevy_reflect/derive/src/impls/func/mod.rs | 2 - .../derive/src/impls/func/reflect_fn.rs | 167 ---------- crates/bevy_reflect/derive/src/impls/mod.rs | 2 +- crates/bevy_reflect/derive/src/lib.rs | 6 - crates/bevy_reflect/src/func/macros.rs | 293 ++++++++++++++++++ crates/bevy_reflect/src/func/mod.rs | 76 ----- 6 files changed, 294 insertions(+), 252 deletions(-) delete mode 100644 crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs diff --git a/crates/bevy_reflect/derive/src/impls/func/mod.rs b/crates/bevy_reflect/derive/src/impls/func/mod.rs index a3e6e9aa64..b092366146 100644 --- a/crates/bevy_reflect/derive/src/impls/func/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/func/mod.rs @@ -1,8 +1,6 @@ pub(crate) use function_impls::impl_function_traits; -pub(crate) use reflect_fn::reflect_fn; mod from_arg; mod function_impls; mod get_ownership; mod into_return; -mod reflect_fn; diff --git a/crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs b/crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs deleted file mode 100644 index d3f1f31284..0000000000 --- a/crates/bevy_reflect/derive/src/impls/func/reflect_fn.rs +++ /dev/null @@ -1,167 +0,0 @@ -use bevy_macro_utils::fq_std::FQResult; -use proc_macro2::Ident; -use quote::{format_ident, quote}; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::{parenthesized, parse_macro_input, token, Block, ExprBlock, Type}; -use syn::{LitStr, ReturnType, Token}; - -struct Arg { - mutability: Option, - name: Ident, - _colon: Token![:], - ty: Type, -} - -impl Parse for Arg { - fn parse(input: ParseStream) -> syn::Result { - let mutability = if input.peek(Token![mut]) { - Some(input.parse()?) - } else { - None - }; - - Ok(Self { - mutability, - name: input.parse()?, - _colon: input.parse()?, - ty: input.parse()?, - }) - } -} - -enum FnName { - #[expect( - dead_code, - reason = "for documenting via the type system what `Anon` expects to parse" - )] - Anon(Token![_]), - Lit(LitStr), - Name(Ident), - Expr(ExprBlock), -} - -impl Parse for FnName { - fn parse(input: ParseStream) -> syn::Result { - let lookahead = input.lookahead1(); - if lookahead.peek(Token![_]) { - Ok(Self::Anon(input.parse()?)) - } else if lookahead.peek(LitStr) { - Ok(Self::Lit(input.parse()?)) - } else if lookahead.peek(syn::Ident) { - Ok(Self::Name(input.parse()?)) - } else if lookahead.peek(token::Brace) { - Ok(Self::Expr(input.parse()?)) - } else { - Err(lookahead.error()) - } - } -} - -struct ReflectFn { - mutability: Option, - movability: Option, - _fn_token: Token![fn], - name: FnName, - _parens: token::Paren, - args: Punctuated, - return_type: ReturnType, - body: Block, -} - -impl Parse for ReflectFn { - fn parse(input: ParseStream) -> syn::Result { - let mutability = if input.peek(Token![mut]) { - Some(input.parse()?) - } else { - None - }; - let movability = if input.peek(Token![move]) { - Some(input.parse()?) - } else { - None - }; - - let content; - Ok(Self { - mutability, - movability, - _fn_token: input.parse()?, - name: input.parse()?, - _parens: parenthesized!(content in input), - args: content.parse_terminated(Arg::parse, Token![,])?, - return_type: input.parse()?, - body: input.parse()?, - }) - } -} - -pub(crate) fn reflect_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as ReflectFn); - - let bevy_reflect_path = crate::meta::get_bevy_reflect_path(); - - let arg_list = format_ident!("args"); - - let dynamic_function = if input.mutability.is_some() { - quote! { #bevy_reflect_path::func::DynamicFunctionMut } - } else { - quote! { #bevy_reflect_path::func::DynamicFunction } - }; - - let movability = &input.movability; - - let extract_args = input.args.iter().map(|arg| { - let mutability = &arg.mutability; - let name = &arg.name; - let ty = &arg.ty; - - quote! { let #mutability #name = #arg_list.take::<#ty>()?; } - }); - - let body = &input.body; - - let info = match &input.name { - FnName::Anon(_) => quote! { #bevy_reflect_path::func::SignatureInfo::anonymous() }, - FnName::Lit(name) => { - quote! { #bevy_reflect_path::func::SignatureInfo::named(#name) } - } - FnName::Name(name) => { - let name = name.to_string(); - quote! { #bevy_reflect_path::func::SignatureInfo::named(#name) } - } - FnName::Expr(expr) => { - quote! { #bevy_reflect_path::func::SignatureInfo::named(#expr) } - } - }; - - let arg_info = input.args.iter().enumerate().map(|(index, arg)| { - let name = &arg.name; - let ty = &arg.ty; - - quote! { - #bevy_reflect_path::func::args::ArgInfo::new::<#ty>(#index).with_name(stringify!(#name)) - } - }); - - let return_ty = match input.return_type { - ReturnType::Default => quote! { () }, - ReturnType::Type(_, ty) => quote! { #ty }, - }; - - proc_macro::TokenStream::from(quote! {{ - #dynamic_function::new( - #[allow(unused_mut)] - #movability |mut #arg_list| { - #(#extract_args)* - #FQResult::Ok(#bevy_reflect_path::func::IntoReturn::into_return(#body)) - }, - #[allow(unused_braces)] - #bevy_reflect_path::func::FunctionInfo::new( - #info - .with_args(::alloc::vec![#(#arg_info),*]) - .with_return::<#return_ty>() - ), - ) - }}) -} diff --git a/crates/bevy_reflect/derive/src/impls/mod.rs b/crates/bevy_reflect/derive/src/impls/mod.rs index f1ea786858..6477c4041e 100644 --- a/crates/bevy_reflect/derive/src/impls/mod.rs +++ b/crates/bevy_reflect/derive/src/impls/mod.rs @@ -2,7 +2,7 @@ mod assertions; mod common; mod enums; #[cfg(feature = "functions")] -pub(crate) mod func; +mod func; mod opaque; mod structs; mod tuple_structs; diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 9b02bc3f16..276371427b 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -826,9 +826,3 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream { }; }) } - -#[cfg(feature = "functions")] -#[proc_macro] -pub fn reflect_fn(input: TokenStream) -> TokenStream { - impls::func::reflect_fn(input) -} diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs index 410aaba456..3699784a7d 100644 --- a/crates/bevy_reflect/src/func/macros.rs +++ b/crates/bevy_reflect/src/func/macros.rs @@ -113,3 +113,296 @@ macro_rules! count_tokens { } pub(crate) use count_tokens; + +/// A helper macro for generating instances of [`DynamicFunction`] and [`DynamicFunctionMut`]. +/// +/// There are some functions that cannot be automatically converted to a dynamic function +/// via [`IntoFunction`] or [`IntoFunctionMut`]. +/// This normally includes functions with more than 15 arguments and functions that +/// return a reference with a lifetime not tied to the first argument. +/// +/// For example, the following fails to compile: +/// +/// ```compile_fail +/// # use bevy_reflect::func::IntoFunction; +/// fn insert_at(index: usize, value: i32, list: &mut Vec) -> &i32 { +/// list.insert(index, value); +/// &list[index] +/// } +/// +/// // This will fail to compile since `IntoFunction` expects return values +/// // to have a lifetime tied to the first argument, but this function +/// // returns a reference tied to the third argument. +/// let func = insert_at.into_function(); +/// ``` +/// +/// In these cases, we normally need to generate the [`DynamicFunction`] manually: +/// +/// ``` +/// # use bevy_reflect::func::{DynamicFunction, IntoFunction, IntoReturn, SignatureInfo}; +/// # fn insert_at(index: usize, value: i32, list: &mut Vec) -> &i32 { +/// # list.insert(index, value); +/// # &list[index] +/// # } +/// let func = DynamicFunction::new( +/// |mut args| { +/// let index = args.take::()?; +/// let value = args.take::()?; +/// let list = args.take::<&mut Vec>()?; +/// +/// let result = insert_at(index, value, list); +/// +/// Ok(result.into_return()) +/// }, +/// SignatureInfo::named("insert_at") +/// .with_arg::("index") +/// .with_arg::("value") +/// .with_arg::<&mut Vec>("list") +/// .with_return::<&i32>(), +/// ); +/// ``` +/// +/// However, this is both verbose and error-prone. +/// What happens if we forget to add an argument to the [`SignatureInfo`]? +/// What if we forget to set the return type? +/// +/// This macro can be used to generate the above code safely, automatically, +/// and with less boilerplate: +/// +/// ``` +/// # use bevy_reflect::func::ArgList; +/// # use bevy_reflect::reflect_fn; +/// # fn insert_at(index: usize, value: i32, list: &mut Vec) -> &i32 { +/// # list.insert(index, value); +/// # &list[index] +/// # } +/// let func = reflect_fn!( +/// fn insert_at(index: usize, value: i32, list: &mut Vec) -> &i32 { +/// insert_at(index, value, list) +/// } +/// ); +/// # // Sanity tests: +/// # let info = func.info(); +/// # assert_eq!(info.name().unwrap(), "insert_at"); +/// # assert_eq!(info.base().arg_count(), 3); +/// # assert_eq!(info.base().args()[0].name(), Some("index")); +/// # assert!(info.base().args()[0].is::()); +/// # assert_eq!(info.base().args()[1].name(), Some("value")); +/// # assert!(info.base().args()[1].is::()); +/// # assert_eq!(info.base().args()[2].name(), Some("list")); +/// # assert!(info.base().args()[2].is::<&mut Vec>()); +/// # assert!(info.base().return_info().is::<&i32>()); +/// # +/// # let mut list = vec![1, 2, 3]; +/// # let args = ArgList::new().push_owned(0_usize).push_owned(5_i32).push_mut(&mut list); +/// # let result = func.call(args).unwrap().unwrap_ref(); +/// # assert_eq!(result.try_downcast_ref::(), Some(&5)); +/// # assert_eq!(list, vec![5, 1, 2, 3]); +/// ``` +/// +/// # Syntax +/// +/// The macro expects the following syntax: +/// +/// ```text +/// MUT MOVE fn NAME ( ARGS ) RETURN BLOCK +/// ``` +/// +/// - `MUT`: `mut` | _none_ +/// - If present, the generated function will instead be a [`DynamicFunctionMut`]. +/// - `MOVE`: `move` | _none_ +/// - If present, adds the `move` keyword to the internal closure. +/// - `NAME`: Block | Identifier | Literal | _none_ +/// - If present, defines the name of the function for the [`SignatureInfo`]. +/// - Blocks should evaluate to a string. Identifiers will be stringified. +/// And Literals will be used as-is. +/// - `ARGS`: ( `mut`? Identifier `:` Type `,`? )* +/// - The list of 0 or more arguments the function accepts. +/// - `RETURN`: `->` Type | _none_ +/// - If present, defines the return type of the function. +/// Otherwise, the return type is assumed to be `()`. +/// - `BLOCK`: Block +/// - The block of code that the function will execute. +/// +/// # Examples +/// +/// Defining anonymous functions: +/// +/// ``` +/// # use bevy_reflect::reflect_fn; +/// let func = reflect_fn!( +/// fn(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// ); +/// +/// let info = func.info(); +/// assert_eq!(info.name(), None); +/// ``` +/// +/// Defining functions with computed names: +/// +/// ``` +/// # use bevy_reflect::reflect_fn; +/// let func = reflect_fn!( +/// fn {concat!("a", "d", "d")} (a: i32, b: i32) -> i32 { +/// a + b +/// } +/// ); +/// +/// let info = func.info(); +/// assert_eq!(info.name().unwrap(), "add"); +/// ``` +/// +/// Defining functions with literal names: +/// +/// ``` +/// # use bevy_reflect::reflect_fn; +/// let func = reflect_fn!( +/// fn "add two numbers" (a: i32, b: i32) -> i32 { +/// a + b +/// } +/// ); +/// +/// let info = func.info(); +/// assert_eq!(info.name().unwrap(), "add two numbers"); +/// ``` +/// +/// Generating a [`DynamicFunctionMut`]: +/// +/// ``` +/// # use bevy_reflect::func::ArgList; +/// # use bevy_reflect::reflect_fn; +/// let mut list = Vec::::new(); +/// let func = reflect_fn!( +/// mut fn push(value: i32) { +/// list.push(value); +/// } +/// ); +/// +/// let args = ArgList::new().push_owned(123); +/// func.call_once(args).unwrap(); +/// assert_eq!(list, vec![123]); +/// ``` +/// +/// Capturing variables with `move`: +/// +/// ``` +/// # use bevy_reflect::reflect_fn; +/// # use std::sync::Arc; +/// let name = Arc::new(String::from("World")); +/// +/// let name_clone = Arc::clone(&name); +/// let func = reflect_fn!( +/// move fn print() { +/// println!("Hello, {}", name_clone); +/// } +/// ); +/// +/// assert_eq!(Arc::strong_count(&name), 2); +/// drop(func); +/// assert_eq!(Arc::strong_count(&name), 1); +/// ``` +/// +/// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut +/// [`IntoFunction`]: crate::func::IntoFunction +/// [`IntoFunctionMut`]: crate::func::IntoFunctionMut +/// [`SignatureInfo`]: crate::func::SignatureInfo +#[macro_export] +macro_rules! reflect_fn { + // === Main === // + (@main [$($mut_:tt)?] [$($move_:tt)?] fn $($name:block)? ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@func $($mut_)?)( + #[allow(unused_variables, unused_mut)] + $($move_)? |mut args| { + $(let $arg_name = args.take::<$arg_ty>()?;)* + let result = $crate::reflect_fn!(@eval $block $(as $ret_ty)?); + ::core::result::Result::Ok($crate::func::IntoReturn::into_return(result)) + }, + $crate::reflect_fn!(@info $($name)?) + $(.with_arg::<$arg_ty>(::core::stringify!($arg_name)))* + $(.with_return::<$ret_ty>())? + ) + }; + + // === Helpers === // + (@func mut) => { + $crate::func::DynamicFunctionMut::new + }; + (@func) => { + $crate::func::DynamicFunction::new + }; + (@info $name:block) => { + $crate::func::SignatureInfo::named($name) + }; + (@info) => { + $crate::func::SignatureInfo::anonymous() + }; + (@eval $block:block as $ty:ty) => { + // We don't actually use `$ty` here since it can lead to `Missing lifetime specifier` errors. + $block + }; + (@eval $block:block) => {{ + // Ensures that `$block` actually evaluates to `()` and isn't relying on type inference, + // which would end up not being reflected by `SignatureInfo` properly. + let temp: () = $block; + temp + }}; + + // === Anonymous === // + (fn ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [] fn ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut fn ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [] fn ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (move fn ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [move] fn ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut move fn ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [move] fn ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + + // === Block Named === // + (fn $name:block ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [] fn $name ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut fn $name:block ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [] fn $name ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (move fn $name:block ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [move] fn $name ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut move fn $name:block ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [move] fn $name ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + + // === Ident Named === // + (fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + + // === Literal Named === // + (fn $name:literal ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [] fn {$name} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut fn $name:literal ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [] fn {$name} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (move fn $name:literal ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [move] fn {$name} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; + (mut move fn $name:literal ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [move] fn {$name} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + }; +} diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index e27a435e1b..f990e135f0 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -189,7 +189,6 @@ pub mod signature; #[cfg(test)] mod tests { - use crate as bevy_reflect; use alloc::borrow::Cow; use super::*; @@ -198,7 +197,6 @@ mod tests { func::args::{ArgError, ArgList, Ownership}, TypePath, }; - use bevy_reflect_derive::reflect_fn; #[test] fn should_error_on_missing_args() { @@ -265,78 +263,4 @@ mod tests { }) ); } - - #[test] - fn should_create_dynamic_function_with_macro() { - let func = reflect_fn!( - fn add(a: i32, b: i32) -> i32 { - a + b - } - ); - - let info = func.info(); - - assert_eq!(info.name().unwrap(), "add"); - assert_eq!(info.base().arg_count(), 2); - assert_eq!(info.base().args()[0].name(), Some("a")); - assert!(info.base().args()[0].is::()); - assert_eq!(info.base().args()[1].name(), Some("b")); - assert!(info.base().args()[1].is::()); - assert!(info.base().return_info().is::()); - - let args = ArgList::new().push_owned(25_i32).push_owned(75_i32); - let result = func.call(args).unwrap().unwrap_owned(); - assert_eq!(result.try_downcast_ref::(), Some(&100)); - } - - #[test] - fn should_create_zero_arg_dynamic_function_with_macro() { - let func = reflect_fn!( - fn shout() { - println!("HEY!!!"); - } - ); - - let info = func.info(); - - assert_eq!(info.name().unwrap(), "shout"); - assert_eq!(info.base().arg_count(), 0); - assert!(info.base().return_info().is::<()>()); - } - - #[test] - fn should_create_anonymous_dynamic_function_with_macro() { - let func = reflect_fn!( - fn _(a: i32, b: i32) -> i32 { - a + b - } - ); - - let info = func.info(); - assert_eq!(info.name(), None); - } - - #[test] - fn should_create_string_named_dynamic_function_with_macro() { - let func = reflect_fn!( - fn "some cool function"(a: i32, b: i32) -> i32 { - a + b - } - ); - - let info = func.info(); - assert_eq!(info.name().unwrap(), "some cool function"); - } - - #[test] - fn should_create_expression_named_dynamic_function_with_macro() { - let func = reflect_fn!( - fn {concat!("some", " cool ", "function")}(a: i32, b: i32) -> i32 { - a + b - } - ); - - let info = func.info(); - assert_eq!(info.name().unwrap(), "some cool function"); - } } From ef8a5056164dc86ca9e596406926331667b36bd7 Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Thu, 12 Dec 2024 14:04:59 -0700 Subject: [PATCH 3/4] Support calling functions in scope --- crates/bevy_reflect/src/func/macros.rs | 47 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs index 3699784a7d..4e88673c67 100644 --- a/crates/bevy_reflect/src/func/macros.rs +++ b/crates/bevy_reflect/src/func/macros.rs @@ -221,11 +221,29 @@ pub(crate) use count_tokens; /// - `RETURN`: `->` Type | _none_ /// - If present, defines the return type of the function. /// Otherwise, the return type is assumed to be `()`. -/// - `BLOCK`: Block -/// - The block of code that the function will execute. +/// - `BLOCK`: Block | _none_ +/// - Optional if `NAME` is an Identifier of a function in scope. +/// In such cases, a Block will be generated that calls `NAME` with `ARGS`. +/// - Otherwise, if present, defines the block of code that the function will execute. /// /// # Examples /// +/// Using a function already in scope: +/// +/// ``` +/// # use bevy_reflect::func::ArgList; +/// # use bevy_reflect::reflect_fn; +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let func = reflect_fn!(fn add(a: i32, b: i32) -> i32); +/// +/// let args = ArgList::new().push_owned(1).push_owned(2); +/// let result = func.call(args).unwrap().unwrap_owned(); +/// assert_eq!(result.try_downcast_ref(), Some(&3)); +/// ``` +/// /// Defining anonymous functions: /// /// ``` @@ -349,6 +367,15 @@ macro_rules! reflect_fn { let temp: () = $block; temp }}; + (@call $name:ident ($($arg_name:ident),*) $block:block) => { + $block + }; + (@call $name:ident ($($arg_name:ident),*)) => {{ + // We could potentially coerce the function to a `fn` pointer here using the given types, + // but that shouldn't really be necessary since it wouldn't compile anyways if given + // incorrect argument types. + $name($($arg_name),*) + }}; // === Anonymous === // (fn ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { @@ -379,17 +406,17 @@ macro_rules! reflect_fn { }; // === Ident Named === // - (fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { - $crate::reflect_fn!(@main [] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + (fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) }; - (mut fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { - $crate::reflect_fn!(@main [mut] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + (mut fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [mut] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) }; - (move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { - $crate::reflect_fn!(@main [] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + (move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) }; - (mut move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { - $crate::reflect_fn!(@main [mut] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) + (mut move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [mut] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) }; // === Literal Named === // From 2283b95757ab1289ad64e67fa6620e14573f618c Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 13 Dec 2024 12:30:42 -0700 Subject: [PATCH 4/4] Add support for full paths --- crates/bevy_reflect/src/func/macros.rs | 62 ++++++++++++++++---------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/crates/bevy_reflect/src/func/macros.rs b/crates/bevy_reflect/src/func/macros.rs index 4e88673c67..35a8fbacf6 100644 --- a/crates/bevy_reflect/src/func/macros.rs +++ b/crates/bevy_reflect/src/func/macros.rs @@ -212,17 +212,18 @@ pub(crate) use count_tokens; /// - If present, the generated function will instead be a [`DynamicFunctionMut`]. /// - `MOVE`: `move` | _none_ /// - If present, adds the `move` keyword to the internal closure. -/// - `NAME`: Block | Identifier | Literal | _none_ +/// - `NAME`: Block | Identifier | `[` Expression Path `]` | Literal | _none_ /// - If present, defines the name of the function for the [`SignatureInfo`]. -/// - Blocks should evaluate to a string. Identifiers will be stringified. -/// And Literals will be used as-is. +/// - Blocks should evaluate to a string, Identifiers will be [stringified], +/// Expression Paths will be evaluated with [`core::any::type_name_of_val`], +/// and Literals will be used as-is. /// - `ARGS`: ( `mut`? Identifier `:` Type `,`? )* /// - The list of 0 or more arguments the function accepts. /// - `RETURN`: `->` Type | _none_ /// - If present, defines the return type of the function. /// Otherwise, the return type is assumed to be `()`. /// - `BLOCK`: Block | _none_ -/// - Optional if `NAME` is an Identifier of a function in scope. +/// - Optional if `NAME` is an `Expression Path` of a function in scope. /// In such cases, a Block will be generated that calls `NAME` with `ARGS`. /// - Otherwise, if present, defines the block of code that the function will execute. /// @@ -233,11 +234,18 @@ pub(crate) use count_tokens; /// ``` /// # use bevy_reflect::func::ArgList; /// # use bevy_reflect::reflect_fn; -/// fn add(a: i32, b: i32) -> i32 { -/// a + b +/// mod math { +/// use std::ops::Add; +/// +/// pub fn add>(a: T, b: T) -> T { +/// a + b +/// } /// } /// -/// let func = reflect_fn!(fn add(a: i32, b: i32) -> i32); +/// let func = reflect_fn!(fn [math::add::](a: i32, b: i32) -> i32); +/// +/// let info = func.info(); +/// assert!(info.name().unwrap().ends_with("math::add")); /// /// let args = ArgList::new().push_owned(1).push_owned(2); /// let result = func.call(args).unwrap().unwrap_owned(); @@ -327,6 +335,7 @@ pub(crate) use count_tokens; /// [`IntoFunction`]: crate::func::IntoFunction /// [`IntoFunctionMut`]: crate::func::IntoFunctionMut /// [`SignatureInfo`]: crate::func::SignatureInfo +/// [stringified]: core::stringify #[macro_export] macro_rules! reflect_fn { // === Main === // @@ -367,15 +376,6 @@ macro_rules! reflect_fn { let temp: () = $block; temp }}; - (@call $name:ident ($($arg_name:ident),*) $block:block) => { - $block - }; - (@call $name:ident ($($arg_name:ident),*)) => {{ - // We could potentially coerce the function to a `fn` pointer here using the given types, - // but that shouldn't really be necessary since it wouldn't compile anyways if given - // incorrect argument types. - $name($($arg_name),*) - }}; // === Anonymous === // (fn ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { @@ -406,17 +406,17 @@ macro_rules! reflect_fn { }; // === Ident Named === // - (fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { - $crate::reflect_fn!(@main [] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) + (fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) }; - (mut fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { - $crate::reflect_fn!(@main [mut] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) + (mut fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) }; - (move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { - $crate::reflect_fn!(@main [] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) + (move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) }; - (mut move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { - $crate::reflect_fn!(@main [mut] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? {$crate::reflect_fn!(@call $name ($($arg_name),*) $($block)?)}) + (mut move fn $name:ident ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { + $crate::reflect_fn!(@main [mut] [move] fn {::core::stringify!($name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) }; // === Literal Named === // @@ -432,4 +432,18 @@ macro_rules! reflect_fn { (mut move fn $name:literal ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $block:block) => { $crate::reflect_fn!(@main [mut] [move] fn {$name} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? $block) }; + + // === In Scope === // + (fn [$name:expr] ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [] [] fn {::core::any::type_name_of_val(&$name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? { $name($($arg_name),*) }) + }; + (mut fn [$name:expr] ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [mut] [] fn {::core::any::type_name_of_val(&$name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? { $name($($arg_name),*) }) + }; + (move fn [$name:expr] ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [] [move] fn {::core::any::type_name_of_val(&$name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? { $name($($arg_name),*) }) + }; + (mut move fn [$name:expr] ($($(mut)? $arg_name:ident : $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)? $($block:block)?) => { + $crate::reflect_fn!(@main [mut] [move] fn {::core::any::type_name_of_val(&$name)} ($($arg_name : $arg_ty),*) $(-> $ret_ty)? { $name($($arg_name),*) }) + }; }