ion/function/
arguments.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::borrow::Cow;
8use std::iter::repeat_with;
9
10use mozjs::jsapi::CallArgs;
11use mozjs::jsval::JSVal;
12
13use crate::conversions::FromValue;
14use crate::function::{Opt, Rest};
15use crate::{Context, Error, ErrorKind, Function, Local, Object, Result, Value};
16
17/// Represents Arguments to a [JavaScript Function](crate::Function).
18/// Wrapper around [CallArgs] to provide lifetimes and root all arguments.
19pub struct Arguments<'cx> {
20	cx: &'cx Context,
21	args: u16,
22	callee: Object<'cx>,
23	call_args: CallArgs,
24}
25
26impl<'cx> Arguments<'cx> {
27	pub unsafe fn new(cx: &'cx Context, argc: u32, vp: *mut JSVal) -> Arguments<'cx> {
28		unsafe {
29			let call_args = CallArgs::from_vp(vp, argc);
30			let callee = cx.root(call_args.callee()).into();
31
32			Arguments {
33				cx,
34				args: u16::try_from(argc).unwrap(),
35				callee,
36				call_args,
37			}
38		}
39	}
40
41	/// Checks if the expected minimum number of arguments were passed.
42	pub fn check_args(&self, cx: &Context, min: u16, key: Option<&str>) -> Result<()> {
43		if self.args < min {
44			let key = key.map_or_else(
45				|| {
46					let callee = Function::from_object(cx, &self.callee).unwrap();
47					callee.name(cx).map(Cow::Owned)
48				},
49				|k| Ok(Cow::Borrowed(k)),
50			)?;
51			let suffix = if min == 1 { "" } else { "s" };
52			let verb = if min == 1 { "was" } else { "were" };
53
54			return Err(Error::new(
55				format!(
56					"{key}: At least {min} argument{suffix} expected, but only {} {verb} passed.",
57					self.args,
58				),
59				ErrorKind::Type,
60			));
61		}
62		Ok(())
63	}
64
65	/// Returns the number of arguments.
66	pub fn len(&self) -> u16 {
67		self.args
68	}
69
70	/// Returns `true` if there are no arguments.
71	pub fn is_empty(&self) -> bool {
72		self.len() == 0
73	}
74
75	/// Returns the context associated with the arguments.
76	pub fn cx(&self) -> &'cx Context {
77		self.cx
78	}
79
80	/// Returns the value of the function being called.
81	pub fn callee(&self) -> Object<'cx> {
82		Object::from(Local::from_handle(self.callee.handle()))
83	}
84
85	/// Returns the `this` value of the function.
86	/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more details.
87	pub fn this(&self) -> Value<'cx> {
88		Value::from(unsafe { Local::from_raw_handle(self.call_args.thisv()) })
89	}
90
91	/// Returns the return value of the function.
92	/// This value can be modified to change the return value.
93	pub fn rval(&mut self) -> Value<'cx> {
94		Value::from(unsafe { Local::from_raw_handle_mut(self.call_args.rval()) })
95	}
96
97	/// Returns the new target of the function if it is constructing.
98	pub fn new_target(&self) -> Option<Value<'cx>> {
99		self.is_constructing()
100			.then(|| Value::from(unsafe { Local::from_raw_handle_mut(self.call_args.new_target()) }))
101	}
102
103	/// Returns the argument at a given index.
104	pub fn value(&self, index: u16) -> Option<Value<'cx>> {
105		if index < self.len() {
106			return Some(Value::from(unsafe {
107				Local::from_raw_handle(self.call_args.get(u32::from(index)))
108			}));
109		}
110		None
111	}
112
113	/// Returns `true` if the function was called with `new`.
114	pub fn is_constructing(&self) -> bool {
115		self.call_args.constructing_()
116	}
117
118	/// Returns `true` if the function ignores the return value.
119	pub fn ignores_return_value(&self) -> bool {
120		self.call_args.ignoresReturnValue_()
121	}
122
123	pub fn call_args(&self) -> &CallArgs {
124		&self.call_args
125	}
126
127	pub fn access<'a>(&'a mut self) -> Accessor<'a, 'cx> {
128		Accessor { args: self, index: 0 }
129	}
130}
131
132pub struct Accessor<'a, 'cx> {
133	args: &'a mut Arguments<'cx>,
134	index: u16,
135}
136
137impl<'cx> Accessor<'_, 'cx> {
138	/// Returns the number of arguments remaining.
139	pub fn len(&self) -> u16 {
140		self.args.len() - self.index
141	}
142
143	/// Returns `true` if there are no arguments remaining.
144	pub fn is_empty(&self) -> bool {
145		self.index == self.args.len()
146	}
147
148	/// Returns the context associated with the arguments.
149	pub fn cx(&self) -> &'cx Context {
150		self.args.cx()
151	}
152
153	/// Returns the value of the function being called.
154	pub fn callee(&self) -> Object<'cx> {
155		self.args.callee()
156	}
157
158	/// Returns the `this` value of the function.
159	/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more details.
160	pub fn this(&self) -> Value<'cx> {
161		self.args.this()
162	}
163
164	/// Returns the argument at the current index.
165	///
166	/// ### Panics
167	/// Panics if there are no arguments remaining.
168	pub fn value(&mut self) -> Value<'cx> {
169		assert!(self.index < self.args.len());
170		let arg = self.args.value(self.index).unwrap();
171		self.index += 1;
172		arg
173	}
174
175	/// Returns `true` if the function was called with `new`.
176	pub fn is_constructing(&self) -> bool {
177		self.args.is_constructing()
178	}
179
180	/// Returns `true` if the function ignores the return value.
181	pub fn ignores_return_value(&self) -> bool {
182		self.args.ignores_return_value()
183	}
184}
185
186pub trait FromArgument<'a, 'cx>: Sized {
187	type Config;
188
189	/// Converts from an argument.
190	fn from_argument(accessor: &'a mut Accessor<'_, 'cx>, config: Self::Config) -> Result<Self>;
191}
192
193impl<'cx> FromArgument<'_, 'cx> for &'cx Context {
194	type Config = ();
195
196	fn from_argument(accessor: &mut Accessor<'_, 'cx>, _: ()) -> Result<&'cx Context> {
197		Ok(accessor.cx())
198	}
199}
200
201impl<'a, 'cx> FromArgument<'a, 'cx> for &'a mut Arguments<'cx> {
202	type Config = ();
203
204	fn from_argument(accessor: &'a mut Accessor<'_, 'cx>, _: ()) -> Result<&'a mut Arguments<'cx>> {
205		Ok(accessor.args)
206	}
207}
208
209impl<'cx, T: FromValue<'cx>> FromArgument<'_, 'cx> for T {
210	type Config = T::Config;
211
212	fn from_argument(accessor: &mut Accessor<'_, 'cx>, config: T::Config) -> Result<T> {
213		T::from_value(accessor.cx(), &accessor.value(), false, config)
214	}
215}
216
217impl<'cx, T: FromValue<'cx>> FromArgument<'_, 'cx> for Opt<T> {
218	type Config = T::Config;
219
220	fn from_argument(accessor: &mut Accessor<'_, 'cx>, config: Self::Config) -> Result<Opt<T>> {
221		if accessor.is_empty() {
222			Ok(Opt(None))
223		} else {
224			T::from_value(accessor.cx(), &accessor.value(), false, config).map(Some).map(Opt)
225		}
226	}
227}
228
229impl<'cx, T: FromValue<'cx>> FromArgument<'_, 'cx> for Rest<T>
230where
231	T::Config: Clone,
232{
233	type Config = T::Config;
234
235	fn from_argument(accessor: &mut Accessor<'_, 'cx>, config: Self::Config) -> Result<Rest<T>> {
236		let len = accessor.len();
237		repeat_with(|| T::from_value(accessor.cx(), &accessor.value(), false, config.clone()))
238			.take(len.into())
239			.collect::<Result<Box<[_]>>>()
240			.map(Rest)
241	}
242}