ion/object/
array.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::iter::FusedIterator;
8use std::ops::{Deref, DerefMut};
9
10use mozjs::gc::RootedVec;
11use mozjs::jsapi::{GetArrayLength, HandleValueArray, IsArray, JSObject, NewArrayObject, NewArrayObject1};
12use mozjs::jsval::{JSVal, ObjectValue};
13use mozjs::rooted;
14
15use crate::conversions::{FromValue, ToValue};
16use crate::flags::{IteratorFlags, PropertyFlags};
17use crate::object::object::ObjectKeysIter;
18use crate::{Context, Local, Object, PropertyDescriptor, Result, Value};
19
20/// Represents an [Array] in the JavaScript Runtime.
21/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) for more details.
22#[derive(Debug)]
23pub struct Array<'a> {
24	arr: Object<'a>,
25}
26
27impl<'a> Array<'a> {
28	/// Creates an empty [Array].
29	pub fn new(cx: &'a Context) -> Array<'a> {
30		Array::new_with_length(cx, 0)
31	}
32
33	/// Creates an empty [Array] with the given length.
34	pub fn new_with_length(cx: &'a Context, length: usize) -> Array<'a> {
35		Array {
36			arr: cx.root(unsafe { NewArrayObject1(cx.as_ptr(), length) }).into(),
37		}
38	}
39
40	/// Creates an [Array] from a slice of values.
41	pub fn from_rooted_vec(cx: &'a Context, vec: &RootedVec<'_, JSVal>) -> Array<'a> {
42		Array::from_handle(cx, HandleValueArray::from(vec))
43	}
44
45	/// Creates an [Array] from a [HandleValueArray].
46	pub fn from_handle(cx: &'a Context, handle: HandleValueArray) -> Array<'a> {
47		Array {
48			arr: cx.root(unsafe { NewArrayObject(cx.as_ptr(), &raw const handle) }).into(),
49		}
50	}
51
52	/// Creates an [Array] from an object.
53	///
54	/// Returns [None] if the object is not an array.
55	pub fn from(cx: &Context, object: Local<'a, *mut JSObject>) -> Option<Array<'a>> {
56		Array::is_array(cx, &object).then(|| Array { arr: object.into() })
57	}
58
59	/// Creates an [Array] from an object.
60	///
61	/// ### Safety
62	/// Object must be an array.
63	pub unsafe fn from_unchecked(object: Local<'a, *mut JSObject>) -> Array<'a> {
64		Array { arr: object.into() }
65	}
66
67	/// Converts an [Array] to a [Vec].
68	/// Returns an empty [Vec] if the conversion fails.
69	pub fn to_vec<'cx>(&self, cx: &'cx Context) -> Vec<Value<'cx>> {
70		let value = cx.root(ObjectValue(self.arr.handle().get())).into();
71		Vec::from_value(cx, &value, true, ()).unwrap_or_default()
72	}
73
74	/// Converts an [Array] to an [Object].
75	pub fn to_object(&self, cx: &'a Context) -> Object<'a> {
76		Object::from(cx.root(self.arr.handle().get()))
77	}
78
79	/// Returns the length of the [Array].
80	pub fn len(&self, cx: &Context) -> u32 {
81		let mut length = 0;
82		unsafe {
83			GetArrayLength(cx.as_ptr(), self.handle().into(), &raw mut length);
84		}
85		length
86	}
87
88	/// Returns if the [Array] is empty.
89	pub fn is_empty(&self, cx: &Context) -> bool {
90		self.len(cx) == 0
91	}
92
93	/// Checks if the [Array] has a value at the given index.
94	pub fn has(&self, cx: &Context, index: u32) -> bool {
95		self.arr.has(cx, index)
96	}
97
98	/// Gets the [Value] at the given index of the [Array].
99	/// Returns [None] if there is no value at the given index.
100	pub fn get<'cx>(&self, cx: &'cx Context, index: u32) -> Result<Option<Value<'cx>>> {
101		self.arr.get(cx, index)
102	}
103
104	/// Gets the value at the given index of the [Array] as a Rust type.
105	/// Returns [None] if there is no value at the given index or conversion to the Rust type fails.
106	pub fn get_as<'cx, T: FromValue<'cx>>(
107		&self, cx: &'cx Context, index: u32, strict: bool, config: T::Config,
108	) -> Result<Option<T>> {
109		self.arr.get_as(cx, index, strict, config)
110	}
111
112	/// Gets the descriptor at the given index of the [Array].
113	/// Returns [None] if there is no value at the given index.
114	pub fn get_descriptor<'cx>(&self, cx: &'cx Context, index: u32) -> Result<Option<PropertyDescriptor<'cx>>> {
115		self.arr.get_descriptor(cx, index)
116	}
117
118	/// Sets the [Value] at the given index of the [Array].
119	/// Returns `false` if the element cannot be set.
120	pub fn set(&self, cx: &Context, index: u32, value: &Value) -> bool {
121		self.arr.set(cx, index, value)
122	}
123
124	/// Sets the Rust type at the given index of the [Array].
125	/// Returns `false` if the element cannot be set.
126	pub fn set_as<'cx, T: ToValue<'cx> + ?Sized>(&self, cx: &'cx Context, index: u32, value: &T) -> bool {
127		self.arr.set_as(cx, index, value)
128	}
129
130	/// Defines the [Value] at the given index of the [Array] with the given attributes.
131	/// Returns `false` if the element cannot be defined.
132	pub fn define(&self, cx: &Context, index: u32, value: &Value, attrs: PropertyFlags) -> bool {
133		self.arr.define(cx, index, value, attrs)
134	}
135
136	/// Defines the Rust type at the given index of the [Array] with the given attributes.
137	/// Returns `false` if the element cannot be defined.
138	pub fn define_as<'cx, T: ToValue<'cx> + ?Sized>(
139		&self, cx: &'cx Context, index: u32, value: &T, attrs: PropertyFlags,
140	) -> bool {
141		self.arr.define_as(cx, index, value, attrs)
142	}
143
144	/// Deletes the [JSVal] at the given index.
145	/// Returns `false` if the element cannot be deleted.
146	pub fn delete(&self, cx: &Context, index: u32) -> bool {
147		self.arr.delete(cx, index)
148	}
149
150	pub fn indices<'cx>(&self, cx: &'cx Context, flags: Option<IteratorFlags>) -> ArrayIndicesIter<'cx> {
151		ArrayIndicesIter(self.arr.keys(cx, flags))
152	}
153
154	pub fn iter<'cx, 's>(&'s self, cx: &'cx Context, flags: Option<IteratorFlags>) -> ArrayIter<'cx, 's>
155	where
156		'a: 'cx,
157	{
158		ArrayIter::new(cx, self, self.indices(cx, flags))
159	}
160
161	/// Checks if a [*mut JSObject] is an array.
162	pub fn is_array_raw(cx: &Context, object: *mut JSObject) -> bool {
163		rooted!(in(cx.as_ptr()) let object = object);
164		let mut is_array = false;
165		unsafe { IsArray(cx.as_ptr(), object.handle().into(), &raw mut is_array) && is_array }
166	}
167
168	/// Checks if a [*mut JSObject] is an array.
169	pub fn is_array(cx: &Context, object: &Local<*mut JSObject>) -> bool {
170		let mut is_array = false;
171		unsafe { IsArray(cx.as_ptr(), object.handle().into(), &raw mut is_array) && is_array }
172	}
173
174	pub fn as_object(&self) -> &Object<'a> {
175		&self.arr
176	}
177
178	pub fn into_local(self) -> Local<'a, *mut JSObject> {
179		self.arr.into_local()
180	}
181}
182
183impl<'a> Deref for Array<'a> {
184	type Target = Local<'a, *mut JSObject>;
185
186	fn deref(&self) -> &Self::Target {
187		&self.arr
188	}
189}
190
191impl DerefMut for Array<'_> {
192	fn deref_mut(&mut self) -> &mut Self::Target {
193		&mut self.arr
194	}
195}
196
197pub struct ArrayIndicesIter<'cx>(ObjectKeysIter<'cx>);
198
199impl Iterator for ArrayIndicesIter<'_> {
200	type Item = u32;
201
202	fn next(&mut self) -> Option<u32> {
203		for key in self.0.by_ref() {
204			let key = key.get();
205			if key.is_int() {
206				return Some(u32::try_from(key.to_int()).unwrap());
207			}
208		}
209		None
210	}
211
212	fn size_hint(&self) -> (usize, Option<usize>) {
213		(0, self.0.size_hint().1)
214	}
215}
216
217impl DoubleEndedIterator for ArrayIndicesIter<'_> {
218	fn next_back(&mut self) -> Option<u32> {
219		while let Some(key) = self.0.next_back() {
220			let key = key.get();
221			if key.is_int() {
222				return Some(u32::try_from(key.to_int()).unwrap());
223			}
224		}
225		None
226	}
227}
228
229impl FusedIterator for ArrayIndicesIter<'_> {}
230
231pub struct ArrayIter<'cx, 'a> {
232	cx: &'cx Context,
233	array: &'a Array<'cx>,
234	indices: ArrayIndicesIter<'cx>,
235}
236
237impl<'cx, 'a> ArrayIter<'cx, 'a> {
238	fn new(cx: &'cx Context, array: &'a Array<'cx>, indices: ArrayIndicesIter<'cx>) -> ArrayIter<'cx, 'a> {
239		ArrayIter { cx, array, indices }
240	}
241}
242
243impl<'cx> Iterator for ArrayIter<'cx, '_> {
244	type Item = (u32, Result<Value<'cx>>);
245
246	fn next(&mut self) -> Option<Self::Item> {
247		self.indices.next().map(|index| {
248			let value = self.array.get(self.cx, index).transpose().unwrap();
249			(index, value)
250		})
251	}
252
253	fn size_hint(&self) -> (usize, Option<usize>) {
254		self.indices.size_hint()
255	}
256}
257
258impl DoubleEndedIterator for ArrayIter<'_, '_> {
259	fn next_back(&mut self) -> Option<Self::Item> {
260		self.indices.next_back().map(|index| {
261			let value = self.array.get(self.cx, index).transpose().unwrap();
262			(index, value)
263		})
264	}
265}
266
267impl FusedIterator for ArrayIter<'_, '_> {}
268
269#[cfg(test)]
270mod tests {
271	use crate::flags::PropertyFlags;
272	use crate::utils::test::TestRuntime;
273	use crate::{Array, Result, Value};
274
275	#[test]
276	fn new() {
277		let rt = TestRuntime::new();
278		let cx = &rt.cx;
279
280		let array = Array::new(cx);
281		assert!(!array.handle().get().is_null());
282		assert!(array.is_empty(cx));
283
284		let array = Array::new_with_length(cx, 8);
285		assert!(!array.handle().get().is_null());
286		assert_eq!(array.len(cx), 8);
287	}
288
289	#[test]
290	fn get_set() {
291		let rt = TestRuntime::new();
292		let cx = &rt.cx;
293
294		const VALUE: i32 = 6;
295		let array = Array::new(cx);
296		assert!(array.set(cx, 0, &Value::i32(cx, VALUE)));
297		assert_eq!(array.len(cx), 1);
298
299		assert!(array.has(cx, 0));
300		let result = array.get(cx, 0).unwrap().unwrap();
301		assert!(result.handle().is_int32());
302		assert_eq!(result.handle().to_int32(), VALUE);
303
304		assert!(!array.has(cx, 1));
305		let result = array.get(cx, 1).unwrap();
306		assert!(result.is_none());
307	}
308
309	#[test]
310	fn get_set_as() {
311		let rt = TestRuntime::new();
312		let cx = &rt.cx;
313
314		let array = Array::new(cx);
315
316		const STRING_VALUE: &str = "spiderfire";
317		assert!(array.set_as(cx, 0, &STRING_VALUE));
318		assert_eq!(array.len(cx), 1);
319
320		assert!(array.has(cx, 0));
321		let result: String = array.get_as(cx, 0, true, ()).unwrap().unwrap();
322		assert_eq!(result, STRING_VALUE);
323
324		const BOOL_VALUE: bool = true;
325		assert!(array.set_as(cx, 1, &BOOL_VALUE));
326		assert_eq!(array.len(cx), 2);
327
328		assert!(array.has(cx, 1));
329		let result: bool = array.get_as(cx, 1, true, ()).unwrap().unwrap();
330		assert_eq!(result, BOOL_VALUE);
331	}
332
333	#[test]
334	fn define_descriptor() {
335		let rt = TestRuntime::new();
336		let cx = &rt.cx;
337
338		let array = Array::new(cx);
339
340		const VALUE: i32 = 6;
341		let flags = PropertyFlags::READ_ONLY | PropertyFlags::PERMANENT;
342		assert!(array.define(cx, 0, &Value::i32(cx, VALUE), flags));
343
344		let result = array.get(cx, 0).unwrap().unwrap();
345		assert!(result.handle().is_int32());
346		assert_eq!(result.handle().to_int32(), VALUE);
347
348		let desc = array.get_descriptor(cx, 0).unwrap().unwrap();
349		assert!(!desc.is_writable());
350		assert!(!desc.is_configurable());
351
352		assert!(array.set(cx, 0, &Value::i32(cx, VALUE + 1)));
353		let result = array.get(cx, 0).unwrap().unwrap();
354		assert!(result.handle().is_int32());
355		assert_eq!(result.handle().to_int32(), VALUE);
356	}
357
358	#[test]
359	fn delete_sparse() {
360		let rt = TestRuntime::new();
361		let cx = &rt.cx;
362
363		let array = Array::new(cx);
364		array.set_as(cx, 0, &0);
365		array.set_as(cx, 1, &1);
366		assert_eq!(array.len(cx), 2);
367
368		assert!(array.delete(cx, 0));
369
370		assert!(!array.has(cx, 0));
371		assert!(array.has(cx, 1));
372		assert_eq!(array.len(cx), 2);
373	}
374
375	#[test]
376	fn iter() {
377		let rt = TestRuntime::new();
378		let cx = &rt.cx;
379
380		let array = Array::new(cx);
381		assert!(array.set_as(cx, 0, &0));
382		assert!(array.set_as(cx, 1, &1));
383		assert!(array.set_as(cx, 3, &3));
384
385		let indices: Vec<_> = array.indices(cx, None).collect();
386		assert_eq!(indices, [0, 1, 3]);
387
388		let pairs: Vec<_> = array
389			.iter(cx, None)
390			.map(|(k, v)| Ok((k, v?.handle().to_int32())))
391			.collect::<Result<_>>()
392			.unwrap();
393		assert_eq!(pairs, [(0, 0), (1, 1), (3, 3)]);
394	}
395}