From e5f2085ddf0b20dd446f713d9b191b04b32e2cfb Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Fri, 6 Sep 2024 10:54:58 -0700 Subject: [PATCH] Added PrettyPrintFunctionInfo Used to help reduce code duplication for overloaded functions and to give users the option to pretty-print FunctionInfo --- .../bevy_reflect/src/func/dynamic_function.rs | 64 ++++++--- .../src/func/dynamic_function_mut.rs | 29 ++-- crates/bevy_reflect/src/func/function_map.rs | 21 ++- crates/bevy_reflect/src/func/info.rs | 124 ++++++++++++++++++ 4 files changed, 195 insertions(+), 43 deletions(-) diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index 33df39bdaf..de780f32ae 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -487,30 +487,17 @@ impl_type_path!((in bevy_reflect) DynamicFunction<'env>); /// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// /// Names for arguments and the function itself are optional and will default to `_` if not provided. +/// +/// If the function is [overloaded], the output will include the signatures of all overloads as a set. +/// For example, `DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})`. +/// +/// [overloaded]: DynamicFunction::with_overload impl<'env> Debug for DynamicFunction<'env> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let name = self.name().unwrap_or(&Cow::Borrowed("_")); - write!(f, "DynamicFunction(fn {name}(")?; - - match self.info() { - FunctionInfoType::Standard(info) => { - for (index, arg) in info.args().iter().enumerate() { - if index > 0 { - write!(f, ", ")?; - } - - let name = arg.name().unwrap_or("_"); - let ty = arg.type_path(); - write!(f, "{name}: {ty}")?; - } - - let ret = info.return_info().type_path(); - write!(f, ") -> {ret})") - } - FunctionInfoType::Overloaded(_) => { - todo!("overloaded functions are not yet debuggable"); - } - } + write!(f, "DynamicFunction(fn {name}")?; + self.function_map.debug(f)?; + write!(f, ")") } } @@ -825,4 +812,39 @@ mod tests { } ); } + + #[test] + fn should_debug_dynamic_function() { + fn greet(name: &String) -> String { + format!("Hello, {}!", name) + } + + let function = greet.into_function(); + let debug = format!("{:?}", function); + assert_eq!(debug, "DynamicFunction(fn bevy_reflect::func::dynamic_function::tests::should_debug_dynamic_function::greet(_: &alloc::string::String) -> alloc::string::String)"); + } + + #[test] + fn should_debug_anonymous_dynamic_function() { + let function = (|a: i32, b: i32| a + b).into_function(); + let debug = format!("{:?}", function); + assert_eq!(debug, "DynamicFunction(fn _(_: i32, _: i32) -> i32)"); + } + + #[test] + fn should_debug_overloaded_dynamic_function() { + fn add>(a: T, b: T) -> T { + a + b + } + + let func = add:: + .into_function() + .with_overload(add::) + .with_name("add"); + let debug = format!("{:?}", func); + assert_eq!( + debug, + "DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})" + ); + } } diff --git a/crates/bevy_reflect/src/func/dynamic_function_mut.rs b/crates/bevy_reflect/src/func/dynamic_function_mut.rs index c57e2650b1..c6bce33145 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_mut.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_mut.rs @@ -382,30 +382,17 @@ impl<'env> DynamicFunctionMut<'env> { /// This takes the format: `DynamicFunctionMut(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// /// Names for arguments and the function itself are optional and will default to `_` if not provided. +/// +/// If the function is [overloaded], the output will include the signatures of all overloads as a set. +/// For example, `DynamicFunctionMut(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})`. +/// +/// [overloaded]: DynamicFunctionMut::with_overload impl<'env> Debug for DynamicFunctionMut<'env> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let name = self.name().unwrap_or(&Cow::Borrowed("_")); - write!(f, "DynamicFunctionMut(fn {name}(")?; - - match self.info() { - FunctionInfoType::Standard(info) => { - for (index, arg) in info.args().iter().enumerate() { - if index > 0 { - write!(f, ", ")?; - } - - let name = arg.name().unwrap_or("_"); - let ty = arg.type_path(); - write!(f, "{name}: {ty}")?; - } - - let ret = info.return_info().type_path(); - write!(f, ") -> {ret})") - } - FunctionInfoType::Overloaded(_) => { - todo!("overloaded functions are not yet debuggable"); - } - } + write!(f, "DynamicFunctionMut(fn {name}")?; + self.function_map.debug(f)?; + write!(f, ")") } } diff --git a/crates/bevy_reflect/src/func/function_map.rs b/crates/bevy_reflect/src/func/function_map.rs index 8377142db2..fb0edc1cb0 100644 --- a/crates/bevy_reflect/src/func/function_map.rs +++ b/crates/bevy_reflect/src/func/function_map.rs @@ -1,7 +1,11 @@ use crate::func::signature::ArgumentSignature; -use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionInfoType, FunctionOverloadError}; +use crate::func::{ + ArgList, FunctionError, FunctionInfo, FunctionInfoType, FunctionOverloadError, + PrettyPrintFunctionInfo, +}; use alloc::borrow::Cow; use bevy_utils::hashbrown::HashMap; +use core::fmt::{Debug, Formatter}; use core::ops::RangeInclusive; /// A helper type for storing a mapping of overloaded functions @@ -216,6 +220,21 @@ impl FunctionMap { } } } + + pub fn debug(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + // `(arg0: i32, arg1: i32) -> ()` + Self::Single(_, info) => PrettyPrintFunctionInfo::new(info).fmt(f), + // `{(arg0: i32, arg1: i32) -> (), (arg0: f32, arg1: f32) -> ()}` + Self::Overloaded(_, infos, _) => { + let mut set = f.debug_set(); + for info in infos.iter() { + set.entry(&PrettyPrintFunctionInfo::new(info)); + } + set.finish() + } + } + } } #[cfg(test)] diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 6787ab35e0..e681f5745a 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -3,6 +3,7 @@ use alloc::{borrow::Cow, vec}; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, vec}; +use core::fmt::{Debug, Formatter}; use core::ops::RangeInclusive; use variadics_please::all_tuples; @@ -211,6 +212,28 @@ impl FunctionInfo { pub fn return_info(&self) -> &ReturnInfo { &self.return_info } + + /// Returns a wrapper around this info that implements [`Debug`] for pretty-printing the function. + /// + /// This can be useful for more readable debugging and logging. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{FunctionInfo, TypedFunction}; + /// # + /// fn add(a: i32, b: i32) -> i32 { + /// a + b + /// } + /// + /// let info = add.get_function_info(); + /// + /// let pretty = info.pretty_printer(); + /// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); + /// ``` + pub fn pretty_printer(&self) -> PrettyPrintFunctionInfo { + PrettyPrintFunctionInfo::new(self) + } } /// Information about the return type of a [`DynamicFunction`] or [`DynamicFunctionMut`]. @@ -240,6 +263,86 @@ impl ReturnInfo { } } +/// A wrapper around [`FunctionInfo`] that implements [`Debug`] for pretty-printing function information. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{FunctionInfo, PrettyPrintFunctionInfo, TypedFunction}; +/// # +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let info = add.get_function_info(); +/// +/// let pretty = PrettyPrintFunctionInfo::new(&info); +/// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); +/// ``` +pub struct PrettyPrintFunctionInfo<'a> { + info: &'a FunctionInfo, + include_fn_token: bool, + include_name: bool, +} + +impl<'a> PrettyPrintFunctionInfo<'a> { + /// Create a new pretty-printer for the given [`FunctionInfo`]. + pub fn new(info: &'a FunctionInfo) -> Self { + Self { + info, + include_fn_token: false, + include_name: false, + } + } + + /// Include the function name in the pretty-printed output. + pub fn include_name(mut self) -> Self { + self.include_name = true; + self + } + + /// Include the `fn` token in the pretty-printed output. + pub fn include_fn_token(mut self) -> Self { + self.include_fn_token = true; + self + } +} + +impl<'a> Debug for PrettyPrintFunctionInfo<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + if self.include_fn_token { + write!(f, "fn")?; + + if self.include_name { + write!(f, " ")?; + } + } + + match (self.include_name, self.info.name()) { + (true, Some(name)) => write!(f, "{}", name)?, + (true, None) => write!(f, "_")?, + _ => {} + } + + write!(f, "(")?; + + // We manually write the args instead of using `DebugTuple` to avoid trailing commas + // and (when used with `{:#?}`) unnecessary newlines + for (index, arg) in self.info.args().iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + + let name = arg.name().unwrap_or("_"); + let ty = arg.type_path(); + write!(f, "{name}: {ty}")?; + } + + let ret = self.info.return_info().type_path(); + write!(f, ") -> {ret}") + } +} + /// A static accessor to compile-time type information for functions. /// /// This is the equivalent of [`Typed`], but for function. @@ -520,4 +623,25 @@ mod tests { assert_eq!(info.args()[1].type_path(), "i32"); assert_eq!(info.return_info().type_path(), "()"); } + + #[test] + fn should_pretty_print_info() { + fn add(a: i32, b: i32) -> i32 { + a + b + } + + let info = add.get_function_info().with_name("add"); + + let pretty = info.pretty_printer(); + assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); + + let pretty = info.pretty_printer().include_fn_token(); + assert_eq!(format!("{:?}", pretty), "fn(_: i32, _: i32) -> i32"); + + let pretty = info.pretty_printer().include_name(); + assert_eq!(format!("{:?}", pretty), "add(_: i32, _: i32) -> i32"); + + let pretty = info.pretty_printer().include_fn_token().include_name(); + assert_eq!(format!("{:?}", pretty), "fn add(_: i32, _: i32) -> i32"); + } }