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"); - } }