//! Types and functions for complex method arguments. //! //! Ruby's APIs to define methods, exposed in magnus through functions such as //! [`define_method`](crate::module::Module::define_method) and the //! [`method`](crate::method!) macro, allow defining methods with a fixed //! number of positional arguments, or an unbounded collection of arguments as //! a slice or Ruby array. The functions in this module allow for more complex //! agument handling. //! //! Ruby arguments can be classified as follows: //! ``` text //! def example(a, b, c=nil, d=nil, *rest, e, f, g:, h:, i: nil, j: nil, **kwargs, &block) //! \__/ \__________/ \___/ \__/ \____/ \____________/ \______/ \____/ //! | | | | | | | | //! required | splat | keywords keywords keywords block //! optional | (required) (optional) (splat) //! | //! trailing //! required //! ``` //! //! The [`scan_args`] function can be used with a method defined as //! `method!(name, -1)` (i.e. receiving a slice of arguments) to implement this //! more complex functionality. //! //! The [`get_kwargs`] function is used to extract keywords from a Ruby `Hash` //! of keywords and implement the behaviour around required and optional //! keyword arguments. //! //! See also [`Ruby`](Ruby#argument-parsing). use std::{ ffi::CString, fmt, mem::transmute, ops::{Bound, RangeBounds}, os::raw::c_int, }; use rb_sys::{rb_error_arity, rb_get_kwargs, rb_scan_args, ID, VALUE}; use seq_macro::seq; use crate::{ block::Proc, error::{protect, Error}, r_array::RArray, r_hash::RHash, try_convert::{TryConvert, TryConvertOwned}, value::{private::ReprValue as _, Id, IntoId, ReprValue, Value}, Ruby, }; struct ArgSpec { required: usize, optional: usize, splat: bool, trailing: usize, keywords: bool, block: bool, } impl ArgSpec { fn new( required: usize, optional: usize, splat: bool, trailing: usize, keywords: bool, block: bool, ) -> Self { if required > 9 { panic!("'required' out of bounds, expected 0..=9, got {}", required); } if optional > 9 { panic!("'optional' out of bounds, expected 0..=9, got {}", required); } if trailing > 9 { panic!("'trailing' out of bounds, expected 0..=9, got {}", required); } Self { required, optional, splat, trailing, keywords, block, } } fn len(&self) -> usize { self.required + self.optional + self.splat as usize + self.trailing + self.keywords as usize + self.block as usize } } impl fmt::Display for ArgSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.required > 0 || self.optional > 0 || self.trailing > 0 { write!(f, "{}", self.required)?; } if self.optional > 0 || (!self.splat && self.trailing > 0) { write!(f, "{}", self.optional)?; } if self.splat { write!(f, "*")?; } if self.trailing > 0 { write!(f, "{}", self.trailing)?; } if self.keywords { write!(f, ":")?; } if self.block { write!(f, "&")?; } Ok(()) } } /// Arguments returned from [`scan_args`]. pub struct Args { /// Required arguments. pub required: Req, /// Optional arguments. pub optional: Opt, /// The splat argument. pub splat: Splat, /// Trailing required arguments. pub trailing: Trail, /// Keywords arguments. pub keywords: Kw, /// This block argument. pub block: Block, } struct ScannedArgs { arg_spec: ArgSpec, args: [Value; 30], parsed: usize, } impl ScannedArgs { fn required(&self) -> &[Value] { &self.args[0..self.arg_spec.required] } fn optional(&self) -> &[Value] { let start = self.arg_spec.required; let end = start + self .arg_spec .optional .min(self.parsed - start - self.arg_spec.trailing); &self.args[start..end] } fn splat(&self) -> Option { self.arg_spec .splat .then(|| self.args[self.arg_spec.required + self.arg_spec.optional]) } fn trailing(&self) -> &[Value] { let start = self.arg_spec.required + self.arg_spec.optional + self.arg_spec.splat as usize; let end = start + self.arg_spec.trailing; &self.args[start..end] } fn keywords(&self) -> Option { self.arg_spec.keywords.then(|| { self.args[self.arg_spec.required + self.arg_spec.optional + self.arg_spec.splat as usize + self.arg_spec.trailing] }) } fn block(&self) -> Option { self.arg_spec.block.then(|| { self.args[self.arg_spec.required + self.arg_spec.optional + self.arg_spec.splat as usize + self.arg_spec.trailing + self.arg_spec.keywords as usize] }) } } mod private { use super::*; pub trait ScanArgsRequired: Sized { const LEN: usize; fn from_slice(vals: &[Value]) -> Result; } macro_rules! impl_scan_args_required { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> ScanArgsRequired for (#(T~N,)*) where #(T~N: TryConvert,)* { const LEN: usize = $n; fn from_slice(vals: &[Value]) -> Result { if vals.len() == ::LEN { Ok(( #(TryConvert::try_convert(vals[N])?,)* )) } else { panic!( "unexpected arguments, expected {} got {}", ::LEN, vals.len() ); } } } }); } } seq!(N in 0..=9 { impl_scan_args_required!(N); }); pub trait ScanArgsOpt: Sized { const LEN: usize; fn from_slice(vals: &[Value]) -> Result; } macro_rules! impl_scan_args_opt { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> ScanArgsOpt for (#(Option,)*) where #(T~N: TryConvert,)* { const LEN: usize = $n; fn from_slice(vals: &[Value]) -> Result { if vals.len() <= ::LEN { Ok(( #(vals.get(N) .filter(|v| !v.is_undef()) .copied() .map(TryConvert::try_convert) .transpose()?,)* )) } else { panic!( "unexpected arguments, expected {} got {}", ::LEN, vals.len() ); } } } }); } } seq!(N in 0..=9 { impl_scan_args_opt!(N); }); pub trait ScanArgsSplat: Sized { const REQ: bool; fn from_opt(val: Option) -> Result; } impl ScanArgsSplat for () { const REQ: bool = false; fn from_opt(val: Option) -> Result { if let Some(val) = val { panic!("unexpected argument {:?}", val); }; Ok(()) } } impl ScanArgsSplat for RArray { const REQ: bool = true; fn from_opt(val: Option) -> Result { TryConvert::try_convert(val.expect("expected splat")) } } impl ScanArgsSplat for Vec where T: TryConvertOwned, { const REQ: bool = true; fn from_opt(val: Option) -> Result { TryConvert::try_convert(val.expect("expected splat")) } } pub trait ScanArgsKw: Sized { const REQ: bool; fn from_opt(val: Option) -> Result; } impl ScanArgsKw for () { const REQ: bool = false; fn from_opt(val: Option) -> Result { if let Some(val) = val { panic!("unexpected argument {:?}", val); }; Ok(()) } } impl ScanArgsKw for RHash { const REQ: bool = true; fn from_opt(val: Option) -> Result { let handle = unsafe { Ruby::get_unchecked() }; let val = val.expect("expected keywords"); if val.is_nil() { return Ok(handle.hash_new()); } TryConvert::try_convert(val) } } pub trait ScanArgsBlock: Sized { const REQ: bool; fn from_opt(val: Option) -> Result; } impl ScanArgsBlock for () { const REQ: bool = false; fn from_opt(val: Option) -> Result { if let Some(val) = val { panic!("unexpected argument {:?}", val); }; Ok(()) } } impl ScanArgsBlock for Proc { const REQ: bool = true; fn from_opt(val: Option) -> Result { let val = val.expect("expected block"); if val.is_nil() { return Err(Error::new( unsafe { Ruby::get_unchecked().exception_arg_error() }, "no block given", )); } TryConvert::try_convert(val) } } impl ScanArgsBlock for Option { const REQ: bool = true; fn from_opt(val: Option) -> Result { TryConvert::try_convert(val.expect("expected block")) } } } /// Trait implemented for types that can be retrieved as required arguments by /// [`scan_args`]. /// /// This trait is implemented for `(T0,)`, `(T0, T1)`, `(T0, T1, T2)`, etc, /// through to a length of 9, where `T0`, `T1`, etc implement [`TryConvert`]. /// /// `()` also implements this trait as a placeholder indicating no required /// arguments are required. pub trait ScanArgsRequired: private::ScanArgsRequired {} impl ScanArgsRequired for T where T: private::ScanArgsRequired {} /// Trait implemented for types that can be retrieved as optional arguments by /// [`scan_args`]. /// /// This trait is implemented for `(Option,)`, `(Option, Option)`, /// etc, through to a length of 9, where `T0`, `T1`, etc implement /// [`TryConvert`]. /// /// `()` also implements this trait as a placeholder indicating no optional /// arguments are required. pub trait ScanArgsOpt: private::ScanArgsOpt {} impl ScanArgsOpt for T where T: private::ScanArgsOpt {} /// Trait implemented for types that can be retrieved a 'splat' argument by /// [`scan_args`]. /// /// This trait is implemented for `Vec` where `T` implements [`TryConvert`] /// and converts to an owned Rust value (not a handle to a Ruby object). It is /// also implemented for [`RArray`]. /// /// `()` also implements this trait as a placeholder indicating no splat /// argument is required. pub trait ScanArgsSplat: private::ScanArgsSplat {} impl ScanArgsSplat for T where T: private::ScanArgsSplat {} /// Trait implemented for types that can be retrieved as keyword arguments by /// [`scan_args`]. /// /// This trait is implemented for [`RHash`]. /// /// `()` also implements this trait as a placeholder indicating no keyword /// arguments are required. pub trait ScanArgsKw: private::ScanArgsKw {} impl ScanArgsKw for T where T: private::ScanArgsKw {} /// Trait implemented for types that can be retrieved as a block argument by /// [`scan_args`]. /// /// This trait is implemented for [`Proc`] and `Option`. /// /// `()` also implements this trait as a placeholder for when no block argument /// is required, although Ruby will still allow a block to be passed, it will /// just ignore it (as is standard for all Ruby methods). pub trait ScanArgsBlock: private::ScanArgsBlock {} impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// Retrieves arguments from a slice. /// /// This function can be used to implement Ruby methods with more complex /// signatures, including optional arguments and 'splats'. /// /// The format of the arguments required is driven by the types in the return /// value. The stuct [`Args`] is returned but the types of its fields are /// determined by type parameters. The type `()` is used as a placeholder when /// a set of arguments is not required. /// /// # Examples /// /// `TCPServer::new`'s argument handling. This is roughly equivalent to /// `def new(hostname=nil, port)`. /// /// ``` /// use magnus::{function, prelude::*, scan_args::scan_args, Error, Ruby, Value}; /// /// fn tcp_svr_init(args: &[Value]) -> Result { /// let args = scan_args(args)?; /// let _: () = args.required; /// let (hostname,): (Option,) = args.optional; /// let _: () = args.splat; /// let (port,): (u16,) = args.trailing; /// let _: () = args.keywords; /// let _: () = args.block; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(2); /// # res.push(hostname)?; /// # res.push(port)?; /// # Ok(res.as_value()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("TCPServer", ruby.class_object())?; /// class.define_singleton_method("new", function!(tcp_svr_init, -1))?; /// # let res = ruby.eval::(r#"TCPServer.new("foo", 1) == ["foo", 1]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"TCPServer.new(2) == [nil, 2]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// The same example as above, specifying the types slightly differently. /// ``` /// use magnus::{function, prelude::*, scan_args::scan_args, Error, Ruby, Value}; /// /// fn tcp_svr_init(args: &[Value]) -> Result { /// let args = scan_args::<(), (Option,), (), (u16,), (), ()>(args)?; /// let (hostname,) = args.optional; /// let (port,) = args.trailing; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(2); /// # res.push(hostname)?; /// # res.push(port)?; /// # Ok(res.as_value()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("TCPServer", ruby.class_object())?; /// class.define_singleton_method("new", function!(tcp_svr_init, -1))?; /// # let res = ruby.eval::(r#"TCPServer.new("foo", 1) == ["foo", 1]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"TCPServer.new(2) == [nil, 2]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// `Addrinfo::getaddrinfo`'s argument handling. This is roughly equivalent to /// `def getaddrinfo(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, timeout: nil)`: /// /// ``` /// use magnus::{function, prelude::*, scan_args::{get_kwargs, scan_args}, Error, Ruby, Symbol, Value}; /// /// fn getaddrinfo(args: &[Value]) -> Result { /// let args = scan_args::<_, _, (), (), _, ()>(args)?; /// let (nodename, service): (String, u16) = args.required; /// let (family, socktype, protocol, flags): ( /// Option, /// Option, /// Option, /// Option, /// ) = args.optional; /// let kw = get_kwargs::<_, (), (Option,), ()>(args.keywords, &[], &["timeout"])?; /// let (timeout,) = kw.optional; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(7); /// # res.push(nodename)?; /// # res.push(service)?; /// # res.push(family)?; /// # res.push(socktype)?; /// # res.push(protocol)?; /// # res.push(flags)?; /// # res.push(timeout)?; /// # Ok(res.as_value()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Addrinfo", ruby.class_object())?; /// class.define_singleton_method("getaddrinfo", function!(getaddrinfo, -1))?; /// # let res = ruby.eval::(r#"Addrinfo.getaddrinfo("a", 1) == ["a", 1, nil, nil, nil, nil, nil]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Addrinfo.getaddrinfo("a", 1, :b, :c, 3, 4, timeout: 5) == ["a", 1, :b, :c, 3, 4, 5]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn scan_args( args: &[Value], ) -> Result, Error> where Req: ScanArgsRequired, Opt: ScanArgsOpt, Splat: ScanArgsSplat, Trail: ScanArgsRequired, Kw: ScanArgsKw, Block: ScanArgsBlock, { let arg_spec = ArgSpec::new( Req::LEN, Opt::LEN, Splat::REQ, Trail::LEN, Kw::REQ, Block::REQ, ); let scanned_args = scan_args_untyped(args, arg_spec)?; Ok(Args { required: Req::from_slice(scanned_args.required())?, optional: Opt::from_slice(scanned_args.optional())?, splat: Splat::from_opt(scanned_args.splat())?, trailing: Trail::from_slice(scanned_args.trailing())?, keywords: Kw::from_opt(scanned_args.keywords())?, block: Block::from_opt(scanned_args.block())?, }) } // Nice-ish interface to rb_scan_args, but returns `Value`s without conversion. fn scan_args_untyped(args: &[Value], arg_spec: ArgSpec) -> Result { let mut out = [unsafe { Ruby::get_unchecked().qnil().as_value() }; 30]; let parsed = unsafe { scan_args_impl(args, &arg_spec.to_string(), &mut out[..arg_spec.len()])? }; Ok(ScannedArgs { arg_spec, args: out, parsed, }) } // Fairly close to rb_scan_args, but Rust types and works around variadic args. // Size of `out` must be >= number of arguments specified in `fmt`. unsafe fn scan_args_impl(args: &[Value], fmt: &str, out: &mut [Value]) -> Result { let out: &mut [VALUE] = transmute(out); let argc = args.len() as c_int; let argv = args.as_ptr() as *const VALUE; let cstr = CString::new(fmt).unwrap(); let fmt = cstr.as_ptr(); let mut result = 0; let handle = Ruby::get_unchecked(); macro_rules! match_arm { ($n:literal) => { seq!(N in 0..$n { protect(|| { result = rb_scan_args( argc, argv, fmt, #(&mut out[N] as *mut VALUE,)* ) as usize; handle.qnil() })? }) } } seq!(N in 0..30 { match out.len() { #(N => match_arm!(N),)* _ => unreachable!(), } }); Ok(result) } /// Arguments returned from [`get_kwargs`]. pub struct KwArgs { /// Required arguments. pub required: Req, /// Optional arguments. pub optional: Opt, /// The splat argument. pub splat: Splat, } /// Deconstruct keyword arguments. /// /// Extracts `required` and `optional` arguments from the given `kw` hash. /// /// The format of the arguments required is driven by the types in the return /// value. The stuct [`KwArgs`] is returned but the types of its fields are /// determined by type parameters. The type `()` is used as a placeholder when /// a set of arguments is not required. /// /// # Panics /// /// This function will panic if `required` or `optional` arguments don't match /// the length of the `Req` and `Opt` type parameters. /// /// # Examples /// /// The rough equivalent of `def test(a:, b:, c: nil, **rest)` would be: /// ``` /// use magnus::{method, prelude::*, scan_args::get_kwargs, Error, RHash, Ruby, Value}; /// # use magnus::IntoValue; /// /// fn test(_rb_self: Value, kw: RHash) -> Result { /// let args = get_kwargs(kw, &["a", "b"], &["c"])?; /// let (a, b): (String, usize) = args.required; /// let (c,): (Option,) = args.optional; /// let rest: RHash = args.splat; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(4); /// # res.push(a)?; /// # res.push(b)?; /// # res.push(c)?; /// # res.push(rest)?; /// # Ok(res.into_value_with(&Ruby::get().unwrap())) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_object().define_method("test", method!(test, 1))?; /// # let res = ruby.eval::(r#"Object.new.test(a: "foo", b: 1, c: true, d: "bar") == ["foo", 1, true, {d: "bar"}]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Object.new.test(a: "foo", b: 1) == ["foo", 1, nil, {}]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// The rough equivalent of `def test(a: "foo")` would be: /// ``` /// use magnus::{ /// method, /// prelude::*, /// scan_args::{get_kwargs, scan_args}, /// Error, Ruby, Value, /// }; /// # use magnus::IntoValue; /// /// fn test(_rb_self: Value, args: &[Value]) -> Result { /// let args = scan_args::<(), (), (), (), _, ()>(args)?; /// let args = get_kwargs(args.keywords, &[], &["a"])?; /// let _: () = args.required; /// let (a,): (Option,) = args.optional; /// let _: () = args.splat; /// let a = a.unwrap_or_else(|| String::from("foo")); /// /// // ... /// # Ok(a.into_value_with(&Ruby::get().unwrap())) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_object() /// .define_method("test", method!(test, -1))?; /// # let res = ruby.eval::(r#"Object.new.test(a: "test") == "test""#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Object.new.test == "foo""#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// or, specifying the types slightly differently: /// ``` /// use magnus::{ /// method, /// prelude::*, /// scan_args::{get_kwargs, scan_args}, /// Error, RHash, Ruby, Value, /// }; /// # use magnus::IntoValue; /// /// fn test(_rb_self: Value, args: &[Value]) -> Result { /// let args = scan_args::<(), (), (), (), RHash, ()>(args)?; /// let args = get_kwargs::<_, (), (Option,), ()>(args.keywords, &[], &["a"])?; /// let (a,) = args.optional; /// let a = a.unwrap_or_else(|| String::from("foo")); /// /// // ... /// # Ok(a.into_value_with(&Ruby::get().unwrap())) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_object() /// .define_method("test", method!(test, -1))?; /// # let res = ruby.eval::(r#"Object.new.test(a: "test") == "test""#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Object.new.test == "foo""#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn get_kwargs( kw: RHash, required: &[T], optional: &[T], ) -> Result, Error> where T: IntoId + Copy, Req: ScanArgsRequired, Opt: ScanArgsOpt, Splat: ScanArgsKw, { assert_eq!(required.len(), Req::LEN); assert_eq!(optional.len(), Opt::LEN); let handle = Ruby::get_with(kw); let ids = required .iter() .copied() .map(|id| id.into_id_with(&handle)) .chain(optional.iter().copied().map(|id| id.into_id_with(&handle))) .collect::>(); let optional_len = if Splat::REQ { -(optional.len() as i8 + 1) } else { optional.len() as i8 }; let mut out = [unsafe { Ruby::get_unchecked().qnil().as_value() }; 19]; let total = Req::LEN + Opt::LEN + Splat::REQ as usize; let mut parsed = 0; unsafe { protect(|| { parsed = rb_get_kwargs( kw.as_rb_value(), ids.as_ptr() as *const ID, required.len() as c_int, optional_len as c_int, out[..total].as_mut_ptr() as *mut VALUE, ) as usize; Ruby::get_unchecked().qnil() })?; }; let opt_end = Req::LEN + Opt::LEN; Ok(KwArgs { required: Req::from_slice(&out[..Req::LEN])?, optional: Opt::from_slice(&out[Req::LEN..opt_end])?, splat: Splat::from_opt(Splat::REQ.then(|| handle.into_value(kw)))?, }) } /// # Argument Parsing /// /// Functions for handling argument parsing. /// /// See also the [`scan_args`] module. impl Ruby { /// Returns `Err` containing a Ruby `ArgumentError` if `len` is not within /// `bounds`. /// /// # Examples /// /// ``` /// use magnus::{function, Error, RString, Ruby, Value}; /// /// fn test(ruby: &Ruby, args: &[Value]) -> Result { /// ruby.check_arity(args.len(), 2..5)?; /// ruby.ary_new_from_values(args).join(", ") /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("test", function!(test, -1)); /// /// assert_eq!( /// ruby.eval::("test(1)").unwrap_err().to_string(), /// "wrong number of arguments (given 1, expected 2..4)" /// ); /// assert_eq!( /// ruby.eval::("test(1, 2, 3, 4, 5)") /// .unwrap_err() /// .to_string(), /// "wrong number of arguments (given 5, expected 2..4)" /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn check_arity(&self, len: usize, bounds: T) -> Result<(), Error> where T: RangeBounds, { if !bounds.contains(&len) { let min = match bounds.start_bound() { Bound::Included(v) => *v as c_int, Bound::Excluded(_) => unreachable!(), Bound::Unbounded => 0, }; let max = match bounds.end_bound() { Bound::Included(v) => *v as c_int, Bound::Excluded(v) if *v == 0 => 0, Bound::Excluded(v) => (v - 1) as c_int, Bound::Unbounded => -1, }; protect(|| { unsafe { rb_error_arity(len as c_int, min, max) }; // we never get here, but this is needed to satisfy the type // system #[allow(unreachable_code)] self.qnil() })?; } Ok(()) } } /// Returns `Err` containing a Ruby `ArgumentError` if `len` is not within /// `bounds`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::check_arity`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// define_global_function, eval, function, scan_args::check_arity, Error, RArray, RString, /// Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn example(args: &[Value]) -> Result { /// check_arity(args.len(), 2..5)?; /// RArray::from_slice(args).join(", ") /// } /// /// define_global_function("example", function!(example, -1)); /// /// assert_eq!( /// eval::("example(1)").unwrap_err().to_string(), /// "wrong number of arguments (given 1, expected 2..4)" /// ); /// assert_eq!( /// eval::("example(1, 2, 3, 4, 5)") /// .unwrap_err() /// .to_string(), /// "wrong number of arguments (given 5, expected 2..4)" /// ); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::check_arity` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn check_arity(len: usize, bounds: T) -> Result<(), Error> where T: RangeBounds, { get_ruby!().check_arity(len, bounds) }