Added arg_count method

This commit is contained in:
Gino Valente 2024-09-05 12:11:13 -07:00
parent f79672def3
commit d662cb1e9e
9 changed files with 138 additions and 39 deletions

View file

@ -13,6 +13,7 @@ use crate::{
use alloc::{borrow::Cow, boxed::Box, sync::Arc}; use alloc::{borrow::Cow, boxed::Box, sync::Arc};
use bevy_reflect_derive::impl_type_path; use bevy_reflect_derive::impl_type_path;
use core::fmt::{Debug, Formatter}; use core::fmt::{Debug, Formatter};
use core::ops::RangeInclusive;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
@ -299,7 +300,7 @@ impl<'env> DynamicFunction<'env> {
let expected_arg_count = self.function_map.info().arg_count(); let expected_arg_count = self.function_map.info().arg_count();
let received_arg_count = args.len(); let received_arg_count = args.len();
if !self.is_overloaded() && expected_arg_count != received_arg_count { if !expected_arg_count.contains(&received_arg_count) {
Err(FunctionError::ArgCountMismatch { Err(FunctionError::ArgCountMismatch {
expected: expected_arg_count, expected: expected_arg_count,
received: received_arg_count, received: received_arg_count,
@ -350,6 +351,29 @@ impl<'env> DynamicFunction<'env> {
pub fn is_overloaded(&self) -> bool { pub fn is_overloaded(&self) -> bool {
self.function_map.is_overloaded() self.function_map.is_overloaded()
} }
/// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will return the minimum and maximum number of arguments.
///
/// Otherwise, the range will have the same start and end.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunction;
/// let add = (|a: i32, b: i32| a + b).into_function();
/// assert_eq!(add.arg_count(), 2..=2);
///
/// let add = add.with_overload(|a: f32, b: f32, c: f32| a + b + c);
/// assert_eq!(add.arg_count(), 2..=3);
/// ```
///
/// [overloaded]: Self::with_overload
pub fn arg_count(&self) -> RangeInclusive<usize> {
self.function_map.arg_count()
}
} }
impl Function for DynamicFunction<'static> { impl Function for DynamicFunction<'static> {
@ -548,13 +572,36 @@ mod tests {
let args = ArgList::default().push_owned(25_i32); let args = ArgList::default().push_owned(25_i32);
let error = func.call(args).unwrap_err(); let error = func.call(args).unwrap_err();
assert!(matches!(
assert_eq!(
error, error,
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 2, expected: 2..=2,
received: 1 received: 1
} }
)); );
}
#[test]
fn should_return_error_on_arg_count_mismatch_overloaded() {
let func = (|a: i32, b: i32| a + b)
.into_function()
.with_overload(|a: i32, b: i32, c: i32| a + b + c);
let args = ArgList::default()
.push_owned(1_i32)
.push_owned(2_i32)
.push_owned(3_i32)
.push_owned(4_i32);
let error = func.call(args).unwrap_err();
assert_eq!(
error,
FunctionError::ArgCountMismatch {
expected: 2..=3,
received: 4
}
);
} }
#[test] #[test]

View file

@ -1,5 +1,6 @@
use alloc::{borrow::Cow, boxed::Box, sync::Arc}; use alloc::{borrow::Cow, boxed::Box, sync::Arc};
use core::fmt::{Debug, Formatter}; use core::fmt::{Debug, Formatter};
use core::ops::RangeInclusive;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
@ -269,7 +270,7 @@ impl<'env> DynamicFunctionMut<'env> {
let expected_arg_count = self.function_map.info().arg_count(); let expected_arg_count = self.function_map.info().arg_count();
let received_arg_count = args.len(); let received_arg_count = args.len();
if !self.is_overloaded() && expected_arg_count != received_arg_count { if !expected_arg_count.contains(&received_arg_count) {
Err(FunctionError::ArgCountMismatch { Err(FunctionError::ArgCountMismatch {
expected: expected_arg_count, expected: expected_arg_count,
received: received_arg_count, received: received_arg_count,
@ -351,6 +352,29 @@ impl<'env> DynamicFunctionMut<'env> {
pub fn is_overloaded(&self) -> bool { pub fn is_overloaded(&self) -> bool {
self.function_map.is_overloaded() self.function_map.is_overloaded()
} }
/// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will return the minimum and maximum number of arguments.
///
/// Otherwise, the range will have the same start and end.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunctionMut;
/// let add = (|a: i32, b: i32| a + b).into_function_mut();
/// assert_eq!(add.arg_count(), 2..=2);
///
/// let add = add.with_overload(|a: f32, b: f32, c: f32| a + b + c);
/// assert_eq!(add.arg_count(), 2..=3);
/// ```
///
/// [overloaded]: Self::with_overload
pub fn arg_count(&self) -> RangeInclusive<usize> {
self.function_map.arg_count()
}
} }
/// Outputs the function's signature. /// Outputs the function's signature.
@ -452,23 +476,23 @@ mod tests {
let args = ArgList::default().push_owned(25_i32); let args = ArgList::default().push_owned(25_i32);
let error = func.call(args).unwrap_err(); let error = func.call(args).unwrap_err();
assert!(matches!( assert_eq!(
error, error,
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 2, expected: 2..=2,
received: 1 received: 1
} }
)); );
let args = ArgList::default().push_owned(25_i32); let args = ArgList::default().push_owned(25_i32);
let error = func.call_once(args).unwrap_err(); let error = func.call_once(args).unwrap_err();
assert!(matches!( assert_eq!(
error, error,
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 2, expected: 2..=2,
received: 1 received: 1
} }
)); );
} }
#[test] #[test]

View file

@ -2,6 +2,7 @@ use crate::func::signature::ArgumentSignature;
use crate::func::{args::ArgError, Return}; use crate::func::{args::ArgError, Return};
use alloc::borrow::Cow; use alloc::borrow::Cow;
use bevy_utils::HashSet; use bevy_utils::HashSet;
use core::ops::RangeInclusive;
use thiserror::Error; use thiserror::Error;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
@ -17,8 +18,11 @@ pub enum FunctionError {
#[error(transparent)] #[error(transparent)]
ArgError(#[from] ArgError), ArgError(#[from] ArgError),
/// The number of arguments provided does not match the expected number. /// The number of arguments provided does not match the expected number.
#[error("expected {expected} arguments but received {received}")] #[error("expected {expected:?} arguments but received {received}")]
ArgCountMismatch { expected: usize, received: usize }, ArgCountMismatch {
expected: RangeInclusive<usize>,
received: usize,
},
/// No overload was found for the given set of arguments. /// No overload was found for the given set of arguments.
#[error("no overload found for arguments with signature `{received:?}`, expected one of `{expected:?}`")] #[error("no overload found for arguments with signature `{received:?}`, expected one of `{expected:?}`")]
NoOverload { NoOverload {

View file

@ -4,6 +4,7 @@ use crate::{
}; };
use alloc::borrow::Cow; use alloc::borrow::Cow;
use core::fmt::Debug; use core::fmt::Debug;
use core::ops::RangeInclusive;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
@ -47,8 +48,15 @@ pub trait Function: PartialReflect + Debug {
/// [`IntoFunction`]: crate::func::IntoFunction /// [`IntoFunction`]: crate::func::IntoFunction
fn name(&self) -> Option<&Cow<'static, str>>; fn name(&self) -> Option<&Cow<'static, str>>;
/// The number of arguments this function accepts. /// Returns the number of arguments the function expects.
fn arg_count(&self) -> usize { ///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will return the minimum and maximum number of arguments.
///
/// Otherwise, the range will have the same start and end.
///
/// [overloaded]: FunctionInfoType::Overloaded
fn arg_count(&self) -> RangeInclusive<usize> {
self.info().arg_count() self.info().arg_count()
} }

View file

@ -3,6 +3,7 @@ use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionInfoType, Functi
use alloc::borrow::Cow; use alloc::borrow::Cow;
use bevy_utils::hashbrown::HashMap; use bevy_utils::hashbrown::HashMap;
use bevy_utils::NoOpHash; use bevy_utils::NoOpHash;
use core::ops::RangeInclusive;
/// A helper type for storing a mapping of overloaded functions /// A helper type for storing a mapping of overloaded functions
/// along with the corresponding [function information]. /// along with the corresponding [function information].
@ -76,6 +77,18 @@ impl<F> FunctionMap<F> {
} }
} }
/// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will return the minimum and maximum number of arguments.
///
/// Otherwise, the range will have the same start and end.
///
/// [overloaded]: Self::Overloaded
pub fn arg_count(&self) -> RangeInclusive<usize> {
self.info().arg_count()
}
/// Merge another [`FunctionMap`] into this one. /// Merge another [`FunctionMap`] into this one.
/// ///
/// If the other map contains any functions with the same signature as this one, /// If the other map contains any functions with the same signature as this one,

View file

@ -3,6 +3,7 @@ use alloc::{borrow::Cow, vec};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
use core::ops::RangeInclusive;
use variadics_please::all_tuples; use variadics_please::all_tuples;
use crate::{ use crate::{
@ -62,21 +63,23 @@ impl IntoIterator for FunctionInfoType<'_> {
} }
impl FunctionInfoType<'_> { impl FunctionInfoType<'_> {
pub fn arg_count(&self) -> usize { /// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will return the minimum and maximum number of arguments.
///
/// Otherwise, the range will have the same start and end.
///
/// [overloaded]: Self::Overloaded
pub fn arg_count(&self) -> RangeInclusive<usize> {
match self { match self {
Self::Standard(info) => info.arg_count(), Self::Standard(info) => RangeInclusive::new(info.arg_count(), info.arg_count()),
Self::Overloaded(infos) => { Self::Overloaded(infos) => infos.iter().map(FunctionInfo::arg_count).fold(
// TODO: This needs proper implementation RangeInclusive::new(usize::MAX, usize::MIN),
infos.iter().map(FunctionInfo::arg_count).min().unwrap() |acc, count| {
} // match self { RangeInclusive::new((*acc.start()).min(count), (*acc.end()).max(count))
// Self::Standard(info) => RangeInclusive::new(info.arg_count(), info.arg_count()), },
// Self::Overloaded(infos) => infos.iter().map(FunctionInfo::arg_count).fold( ),
// RangeInclusive::new(0, 0),
// |acc, count| {
// RangeInclusive::new((*acc.start()).min(count), (*acc.end()).max(count))
// },
// ),
// }
} }
} }
} }

View file

@ -178,7 +178,7 @@ mod tests {
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 1, expected: 1..=1,
received: 0 received: 0
} }
); );
@ -194,7 +194,7 @@ mod tests {
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 0, expected: 0..=0,
received: 1 received: 1
} }
); );

View file

@ -96,7 +96,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }
@ -125,7 +125,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }
@ -155,7 +155,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }
@ -185,7 +185,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }

View file

@ -102,7 +102,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }
@ -131,7 +131,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }
@ -161,7 +161,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }
@ -191,7 +191,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: COUNT..=COUNT,
received: args.len(), received: args.len(),
}); });
} }