Added PrettyPrintFunctionInfo

Used to help reduce code duplication for overloaded functions
and to give users the option to pretty-print FunctionInfo
This commit is contained in:
Gino Valente 2024-09-06 10:54:58 -07:00
parent c2a18d593f
commit e5f2085ddf
4 changed files with 195 additions and 43 deletions

View file

@ -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<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
let func = add::<i32>
.into_function()
.with_overload(add::<f32>)
.with_name("add");
let debug = format!("{:?}", func);
assert_eq!(
debug,
"DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})"
);
}
}

View file

@ -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, ")")
}
}

View file

@ -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<F> FunctionMap<F> {
}
}
}
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)]

View file

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