diff --git a/gdnative-core/src/export/method.rs b/gdnative-core/src/export/method.rs index 756a11f9d..e7a50468d 100644 --- a/gdnative-core/src/export/method.rs +++ b/gdnative-core/src/export/method.rs @@ -1,8 +1,8 @@ //! Method registration use std::borrow::Cow; -use std::fmt; use std::marker::PhantomData; +use std::{fmt, ops}; use crate::core_types::{FromVariant, FromVariantError, Variant}; use crate::export::class::NativeClass; @@ -209,17 +209,45 @@ impl> Method for StaticArgs { } /// Safe interface to a list of borrowed method arguments with a convenient API -/// for common operations with them. Can also be used as an iterator. +/// for common operations with them. +/// +/// **Note:** the `impl Iterator` is deprecated, `Varargs` itself should not be treated as a consumable iterator. +/// Instead, use [`Self::as_slice()`]. +/// +/// This type can be destructured into tuples: +/// ```no_run +/// use gdnative::prelude::*; +/// use gdnative::export::Varargs; +/// +/// #[derive(NativeClass)] +/// #[no_constructor] +/// struct MyClass {} +/// +/// struct CalcMethod; +/// impl Method for CalcMethod { +/// fn call( +/// &self, +/// _this: TInstance<'_, MyClass>, +/// args: Varargs<'_>, +/// ) -> Variant { +/// // Destructure via try_into(): +/// let (a, b): (i64, i64) = args.try_into().expect("signature mismatch"); +/// let ret = a * b; +/// ret.to_variant() +/// } +/// } +/// ``` pub struct Varargs<'a> { idx: usize, - iter: std::slice::Iter<'a, &'a Variant>, + args: &'a [&'a Variant], + offset_index: usize, } impl<'a> Varargs<'a> { /// Returns the amount of arguments left. #[inline] pub fn len(&self) -> usize { - self.iter.len() + self.args.len() - self.idx } #[inline] @@ -250,7 +278,7 @@ impl<'a> Varargs<'a> { /// Returns the remaining arguments as a slice of `Variant`s. #[inline] pub fn as_slice(&self) -> &'a [&'a Variant] { - self.iter.as_slice() + self.args } /// Discard the rest of the arguments, and return an error if there is any. @@ -284,7 +312,87 @@ impl<'a> Varargs<'a> { let args = std::mem::transmute::<&[*mut sys::godot_variant], &[&Variant]>(args); Self { idx: 0, - iter: args.iter(), + args, + offset_index: 0, + } + } + + /// Check the length of arguments. + /// + /// See [`Self::get()`] or [`Self::get_opt()`] for examples. + /// + /// # Errors + /// Returns an [`VarargsError::InvalidLength`] if the length of arguments is outside the specified range. + #[inline] + pub fn check_length(&self, expected: impl Into) -> Result<(), VarargsError> { + let passed = self.args.len(); + let expected = expected.into(); + if expected.contains(passed) { + Ok(()) + } else { + // Note: cannot use Box> because trait is not object-safe due to _unrelated_ method contains() + Err(VarargsError::InvalidLength { + length: passed, + expected, + }) + } + } + + /// Returns the type-converted value at the specified argument position. + /// + /// # Errors + /// Returns a [`VarargsError::InvalidArgumentType`] if the conversion fails or the argument is not set. + /// + /// # Examples + /// ``` + /// # fn call(args: gdnative::export::Varargs) -> Result<(), Box> { + /// args.check_length(2)?; + /// let a: usize = args.get(0)?; + /// let rest: i64 = args.get(1)?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn get(&self, index: usize) -> Result { + // Note: disregards iterator offset, since that representation is deprecated + + match self.args.get(index) { + Some(v) => match T::from_variant(v) { + Ok(ok) => Ok(ok), + Err(error) => Err(VarargsError::InvalidArgumentType { index, error }), + }, + None => { + let error = FromVariantError::Custom("Argument is not set".to_owned()); + Err(VarargsError::InvalidArgumentType { index, error }) + } + } + } + + /// Returns the type-converted value at the specified argument position. + /// Returns `None` if the argument is not set. + /// + /// # Errors + /// Returns a [`VarargsError::InvalidArgumentType`] if the conversion fails. + /// + /// # Examples + /// ``` + /// # fn call(args: gdnative::export::Varargs) -> Result<(), Box> { + /// args.check_length(1..=2)?; + /// let a: usize = args.get(0)?; + /// let rest: i64 = args.get_opt(1)?.unwrap_or(72); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn get_opt(&self, index: usize) -> Result, VarargsError> { + // Note: disregards iterator offset, since that representation is deprecated + + match self.args.get(index) { + Some(v) => match T::from_variant(v) { + Ok(ok) => Ok(Some(ok)), + Err(error) => Err(VarargsError::InvalidArgumentType { index, error }), + }, + None => Ok(None), } } } @@ -293,7 +401,197 @@ impl<'a> Iterator for Varargs<'a> { type Item = &'a Variant; #[inline] fn next(&mut self) -> Option { - self.iter.next().copied() + let ret = self.args.get(self.idx); + ret.map(|&v| { + self.idx += 1; + v + }) + } +} + +// Return a second token. +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} + +// Count parameters. +macro_rules! count_tts { + ($($tts:tt)*) => { + 0usize $(+ replace_expr!($tts 1usize))* + }; +} + +// Convert from Varargs to tuples, implement traits. +macro_rules! varargs_into_tuple { + ($($params:ident),*) => { + impl<'a, $($params: FromVariant),*> std::convert::TryFrom> for ($($params,)*) { + type Error = VarargsError; + + #[inline] + fn try_from(args: Varargs<'a>) -> Result { + const EXPECTED: usize = count_tts!($($params)*); + args.check_length(EXPECTED)?; + let mut i: usize = 0; + #[allow(unused_variables, unused_mut)] + let mut inc = || { + let ret = i; + i += 1; + ret + }; + Ok(( + $(args.get::<$params>(inc())?,)* + )) + } + } + }; +} + +// Define up to the length supported by standard library. +varargs_into_tuple!(); +varargs_into_tuple!(A); +varargs_into_tuple!(A, B); +varargs_into_tuple!(A, B, C); +varargs_into_tuple!(A, B, C, D); +varargs_into_tuple!(A, B, C, D, E); +varargs_into_tuple!(A, B, C, D, E, F); +varargs_into_tuple!(A, B, C, D, E, F, G); +varargs_into_tuple!(A, B, C, D, E, F, G, H); +varargs_into_tuple!(A, B, C, D, E, F, G, H, I); +varargs_into_tuple!(A, B, C, D, E, F, G, H, I, J); +varargs_into_tuple!(A, B, C, D, E, F, G, H, I, J, K); +varargs_into_tuple!(A, B, C, D, E, F, G, H, I, J, K, L); + +/// All possible errors that can occur when converting from Varargs. +/// +/// For methods that return this error, see [`Varargs::check_length()`], [`Varargs::get()`] or [`Varargs::get_opt()`]. +/// Another context where this type is used is when destructuring `Varargs` into tuples. +#[derive(Debug)] +pub enum VarargsError { + /// At least one argument type mismatches. + InvalidArgumentType { + index: usize, + error: FromVariantError, + }, + /// Number of arguments doesn't match expectations. + InvalidLength { + /// The number of arguments actually passed. + length: usize, + expected: IndexBounds, + }, +} + +impl std::error::Error for VarargsError {} + +impl fmt::Display for VarargsError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + VarargsError::InvalidArgumentType { index, error } => { + write!(f, "type error for argument #{}: {}", index, error)? + } + VarargsError::InvalidLength { expected, length } => write!( + f, + "length mismatch: expected range {}, actual {}", + expected, length + )?, + } + + Ok(()) + } +} + +/// Defines which number of arguments is valid. +/// +/// Convertible from an exact value `a` as well as inclusive range expressions `a..`, `..=b`, `a..=b`. +pub struct IndexBounds { + /// The lower (inclusive) bound of the expected number of arguments, or `None` if unbounded. + pub start: Option, + + /// The upper (inclusive) bound of the expected number of arguments, or `None` if unbounded. + pub end: Option, +} + +impl IndexBounds { + #[inline] + pub fn contains(&self, value: usize) -> bool { + match (self.start, self.end) { + (Some(s), Some(e)) => value >= s && value <= e, + (Some(s), None) => value >= s, + (None, Some(e)) => value <= e, + (None, None) => false, // unreachable in this context, but can be constructed by user + } + } +} + +/// `a` +impl From for IndexBounds { + #[inline] + fn from(exact_value: usize) -> Self { + Self { + start: Some(exact_value), + end: Some(exact_value), + } + } +} + +/// `a..=b` +impl From> for IndexBounds { + #[inline] + fn from(range: ops::RangeInclusive) -> Self { + Self { + start: Some(*range.start()), + end: Some(*range.end()), + } + } +} + +/// `a..` +impl From> for IndexBounds { + #[inline] + fn from(range: ops::RangeFrom) -> Self { + Self { + start: Some(range.start), + end: None, + } + } +} + +/// `..=b` +impl From> for IndexBounds { + #[inline] + fn from(range: ops::RangeToInclusive) -> Self { + Self { + start: None, + end: Some(range.end), + } + } +} + +impl fmt::Debug for IndexBounds { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "IndexBounds({})", self) + } +} + +impl fmt::Display for IndexBounds { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.start { + Some(start) => write!(f, "{}", start)?, + None => {} + } + + write!(f, "..=")?; + + match self.end { + Some(end) => write!(f, "{}", end)?, + None => {} + } + + Ok(()) } } @@ -361,10 +659,11 @@ impl<'r, 'a, T: FromVariant> ArgBuilder<'r, 'a, T> { #[inline] pub fn get(mut self) -> Result> { self.get_optional_internal().and_then(|arg| { + let actual_index = self.args.idx + self.args.offset_index; arg.ok_or(ArgumentError { site: self.site, kind: ArgumentErrorKind::Missing { - idx: self.args.idx, + idx: actual_index, name: self.name, }, }) @@ -389,14 +688,13 @@ impl<'r, 'a, T: FromVariant> ArgBuilder<'r, 'a, T> { ty, .. } = self; - let idx = args.idx; + let actual_index = args.idx + args.offset_index; - if let Some(arg) = args.iter.next() { - args.idx += 1; + if let Some(arg) = args.next() { T::from_variant(arg).map(Some).map_err(|err| ArgumentError { site: *site, kind: ArgumentErrorKind::CannotConvert { - idx, + idx: actual_index, name: name.take(), value: arg, ty: ty diff --git a/test/src/test_register.rs b/test/src/test_register.rs index bfb7aa098..535c08610 100644 --- a/test/src/test_register.rs +++ b/test/src/test_register.rs @@ -1,3 +1,4 @@ +use std::error::Error; use std::ops::Add; use gdnative::export::{StaticArgs, StaticArgsMethod, StaticallyNamed}; @@ -8,6 +9,8 @@ pub(crate) fn run_tests() -> bool { status &= test_register_property(); status &= test_advanced_methods(); + status &= test_varargs_gets(); + status &= test_varargs_to_tuple(); status } @@ -16,6 +19,8 @@ pub(crate) fn register(handle: InitHandle) { handle.add_class::(); handle.add_class::(); handle.add_class::(); + handle.add_class::(); + handle.add_class::(); } #[derive(Copy, Clone, Debug, Default)] @@ -206,3 +211,90 @@ crate::godot_itest! { test_advanced_methods { approx::assert_relative_eq!(3.5, v.x); approx::assert_relative_eq!(-0.5, v.y); }} + +#[derive(NativeClass)] +#[inherit(Reference)] +#[register_with(VarargsGets::register)] +struct VarargsGets {} + +#[methods] +impl VarargsGets { + fn new(_owner: TRef) -> Self { + Self {} + } + + fn register(builder: &ClassBuilder) { + builder.method("calc", CalcMethod).done(); + } +} + +struct CalcMethod; + +impl Method for CalcMethod { + fn call( + &self, + _this: TInstance<'_, VarargsGets>, + args: gdnative::export::Varargs<'_>, + ) -> Variant { + (|| { + args.check_length(1..=3)?; + let a: i64 = args.get(0)?; + let b: i64 = args.get(1)?; + let c: i64 = args.get_opt(2)?.unwrap_or(11); + + let ret = a * b - c; + Ok::>(ret.to_variant()) + })() + .unwrap_or_default() + } +} + +crate::godot_itest! { test_varargs_gets { + let thing = Instance::::new(); + let base = thing.base(); + + let args = [3_i64.to_variant(), 4_i64.to_variant(), 5_i64.to_variant()]; + assert_eq!(unsafe { base.call("calc", &args).to() }, Some(7)); + + let args = [3_i64.to_variant(), 4_i64.to_variant()]; + assert_eq!(unsafe { base.call("calc", &args).to() }, Some(1)); +}} + +#[derive(NativeClass)] +#[inherit(Reference)] +#[register_with(VarargsToTuple::register)] +struct VarargsToTuple {} + +#[methods] +impl VarargsToTuple { + fn new(_owner: TRef) -> Self { + VarargsToTuple {} + } + + fn register(builder: &ClassBuilder) { + builder.method("calc", CalcMethod2).done(); + } +} + +struct CalcMethod2; + +impl Method for CalcMethod2 { + fn call( + &self, + _this: TInstance<'_, VarargsToTuple>, + args: gdnative::export::Varargs<'_>, + ) -> Variant { + let (a, b, c): (i64, i64, i64) = + std::convert::TryInto::try_into(args).expect("Must be able to convert"); + let ret = a * b - c; + ret.to_variant() + } +} + +crate::godot_itest! { test_varargs_to_tuple { + let thing = Instance::::new(); + let base = thing.base(); + + let args = [3_i64.to_variant(), 4_i64.to_variant(), 5_i64.to_variant()]; + assert_eq!(unsafe { base.call("calc", &args).to() }, Some(7)); +}}