diff --git a/gdnative-core/src/export/method.rs b/gdnative-core/src/export/method.rs index 756a11f9d..1b13d3b9f 100644 --- a/gdnative-core/src/export/method.rs +++ b/gdnative-core/src/export/method.rs @@ -1,8 +1,10 @@ //! Method registration use std::borrow::Cow; +use std::convert::TryInto; use std::fmt; use std::marker::PhantomData; +use std::ops::{Bound, RangeBounds}; use crate::core_types::{FromVariant, FromVariantError, Variant}; use crate::export::class::NativeClass; @@ -212,14 +214,15 @@ impl> Method for StaticArgs { /// for common operations with them. Can also be used as an iterator. 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 +253,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,16 +287,303 @@ 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 `get()`, `get_opt()` or `get_rest()` for examples. + /// + /// # Errors + /// Returns an error if the length of arguments is outside the specified range. + #[inline] + pub fn check_length(&self, bounds: impl RangeBounds) -> Result<(), ArgumentLengthError> { + let passed = self.args.len(); + if bounds.contains(&passed) { + Ok(()) + } else { + Err(ArgumentLengthError::new(passed, bounds)) + } + } + + /// Returns the type-converted value at the specified argument position. + /// + /// # Errors + /// Returns an error if the conversion fails or the argument is not set. + /// + /// # Examples + /// ``` + /// # fn foo(args: gdnative::export::Varargs) -> Result<(), Box> { + /// args.check_length(2..=2)?; + /// let a: usize = args.get(0)?; + /// let b: i64 = args.get(1)?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn get(&self, index: usize) -> Result { + let relative_index = index; + let actual_index = index + self.offset_index; + + match self.args.get(relative_index) { + Some(v) => match T::from_variant(v) { + Ok(ok) => Ok(ok), + Err(err) => Err(ArgumentTypeError::new(actual_index, err)), + }, + None => { + let err = FromVariantError::Custom("Argument is not set".to_owned()); + Err(ArgumentTypeError::new(actual_index, err)) + } + } + } + + /// Returns the type-converted value at the specified argument position. + /// Returns `None` if the argument is not set. + /// + /// # Errors + /// Returns an error if the conversion fails. + /// + /// # Examples + /// ``` + /// # fn foo(args: gdnative::export::Varargs) -> Result<(), Box> { + /// args.check_length(1..=2)?; + /// let a: usize = args.get(0)?; + /// let b: i64 = args.get_opt(1)?.unwrap_or(72); + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn get_opt(&self, index: usize) -> Result, ArgumentTypeError> { + let relative_index = index; + let actual_index = index + self.offset_index; + + match self.args.get(relative_index) { + Some(v) => match T::from_variant(v) { + Ok(ok) => Ok(Some(ok)), + Err(err) => Err(ArgumentTypeError::new(actual_index, err)), + }, + None => Ok(None), } } + + /// Returns the type-converted value from the specified argument position. + /// Can be converted to any type that implements TryFrom. + /// + /// # Errors + /// Returns an error if the conversion fails. + /// + /// # Examples + /// ```ignore + /// # fn foo(args: gdnative::export::Varargs) -> Result<(), Box> { + /// args.check_length(1..)?; + /// let a: usize = args.get(0)?; + /// let rest: Vec = args.get_rest(1)?; + /// # Ok(()) + /// # } + /// ``` + #[inline] + pub fn get_rest(&self, rest_index: usize) -> Result as TryInto>::Error> + where + Varargs<'a>: TryInto, + { + let relative_rest_index = rest_index; + let actual_rest_index = rest_index + self.offset_index; + + let rest = self.args.get(relative_rest_index..).unwrap_or_default(); + let varargs = Varargs::<'a> { + idx: 0, + args: rest, + offset_index: actual_rest_index, + }; + varargs.try_into() + } + + /// Get the varargs's offset index. + #[inline] + #[must_use] + pub fn offset_index(&self) -> usize { + self.offset_index + } } 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).copied(); + if ret.is_some() { + self.idx += 1; + } + ret + } +} + +/// All possible error types for convert from Varargs. +#[derive(Debug)] +pub enum VarargsError { + ArgumentTypeError(ArgumentTypeError), + ArgumentLengthError(ArgumentLengthError), +} + +impl std::error::Error for VarargsError {} +impl std::fmt::Display for VarargsError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + VarargsError::ArgumentTypeError(e) => e.fmt(f), + VarargsError::ArgumentLengthError(e) => e.fmt(f), + } + } +} + +impl From for VarargsError { + #[inline] + fn from(value: ArgumentTypeError) -> Self { + Self::ArgumentTypeError(value) + } +} + +impl From for VarargsError { + #[inline] + fn from(value: ArgumentLengthError) -> Self { + Self::ArgumentLengthError(value) + } +} + +/// Error to incorrect type of argument. +/// Displays a message containing the position of the argument and cause of the failure to convert. +#[derive(Debug)] +pub struct ArgumentTypeError { + index: usize, + nested_error: FromVariantError, +} + +impl ArgumentTypeError { + /// Create a new error with the argument position and `FromVariantError`. + #[inline] + #[must_use] + pub fn new(index: usize, nested_error: FromVariantError) -> Self { + Self { + index, + nested_error, + } + } + + /// Returns an ordinal number representation. + #[inline] + #[must_use] + fn ordinal(&self) -> String { + match self.index + 1 { + 1 => "1st".to_owned(), + 2 => "2nd".to_owned(), + 3 => "3rd".to_owned(), + i @ 4.. => format!("{i}th"), + _ => "unknown".to_owned(), + } + } + + /// Get the argument type error's index. + #[inline] + #[must_use] + pub fn index(&self) -> usize { + self.index + } + + /// Get a reference to the argument type error's nested error. + #[inline] + #[must_use] + pub fn nested_error(&self) -> &FromVariantError { + &self.nested_error + } +} + +impl std::error::Error for ArgumentTypeError {} +impl std::fmt::Display for ArgumentTypeError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Incorrect type of {} argument, cause: {}", + self.ordinal(), + self.nested_error, + ) + } +} + +/// Error to argument lengths do not match. +/// Display a message containing the length of arguments passed and the expected range of lengths. +#[derive(Debug)] +pub struct ArgumentLengthError { + passed: usize, + expected: (Bound, Bound), +} + +impl ArgumentLengthError { + /// Creates a new error with the length of the arguments passed and the expected arguments range. + #[inline] + #[must_use] + pub fn new(passed: usize, expected: impl RangeBounds) -> Self { + Self { + passed, + expected: ( + expected.start_bound().cloned(), + expected.end_bound().cloned(), + ), + } + } + + /// Get the argument length error's passed. + #[inline] + #[must_use] + pub fn passed(&self) -> usize { + self.passed + } + + /// Get the argument length error's expected min. + #[inline] + #[must_use] + pub fn expected_min(&self) -> usize { + match self.expected.0 { + Bound::Included(s) => s, + Bound::Excluded(s) => s + 1, + Bound::Unbounded => usize::MIN, + } + } + + /// Get the argument length error's expected max. + #[inline] + #[must_use] + pub fn expected_max(&self) -> usize { + match self.expected.1 { + Bound::Included(e) => e, + Bound::Excluded(e) => e - 1, + Bound::Unbounded => usize::MAX, + } + } +} + +impl std::error::Error for ArgumentLengthError {} +impl std::fmt::Display for ArgumentLengthError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let expected_msg = match (self.expected_min(), self.expected_max()) { + (usize::MIN, usize::MAX) => "any".to_owned(), + (usize::MIN, e) => format!("max {e}"), + (s, usize::MAX) => format!("min {s}"), + (s, e) => { + if s == e { + s.to_string() + } else { + format!("min {s} and max {e}") + } + } + }; + write!( + f, + "Argument lengths do not match, passed {}, expected {}", + self.passed, expected_msg + ) } } @@ -361,10 +651,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 +680,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 680d07c7a..533bc4b3e 100644 --- a/test/src/test_register.rs +++ b/test/src/test_register.rs @@ -1,13 +1,15 @@ +use std::error::Error; use std::ops::Add; use gdnative::export::{StaticArgs, StaticArgsMethod, StaticallyNamed}; -use gdnative::prelude::*; +use gdnative::{log, prelude::*}; pub(crate) fn run_tests() -> bool { let mut status = true; status &= test_register_property(); status &= test_advanced_methods(); + status &= test_varargs_gets(); status } @@ -16,6 +18,7 @@ pub(crate) fn register(handle: InitHandle) { handle.add_class::(); handle.add_class::(); handle.add_class::(); + handle.add_class::(); } #[derive(Copy, Clone, Debug, Default)] @@ -232,3 +235,64 @@ fn test_advanced_methods() -> bool { ok } + +#[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_else(|err| { + log::error(log::godot_site!(calc), err); + Variant::nil() + }) + } +} + +fn test_varargs_gets() -> bool { + println!(" -- test_varargs_gets"); + + let ok = std::panic::catch_unwind(|| { + 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)); + }) + .is_ok(); + + if !ok { + godot_error!(" !! Test test_varargs_gets failed"); + } + ok +}