boa/object/operations.rs
1use crate::{
2 builtins::Array,
3 object::JsObject,
4 property::{PropertyDescriptor, PropertyKey, PropertyNameKind},
5 symbol::WellKnownSymbols,
6 value::Type,
7 Context, JsResult, JsValue,
8};
9
10/// Object integrity level.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum IntegrityLevel {
13 /// Sealed object integrity level.
14 ///
15 /// Preventing new properties from being added to it and marking all existing
16 /// properties as non-configurable. Values of present properties can still be
17 /// changed as long as they are writable.
18 Sealed,
19
20 /// Frozen object integrity level
21 ///
22 /// A frozen object can no longer be changed; freezing an object prevents new
23 /// properties from being added to it, existing properties from being removed,
24 /// prevents changing the enumerability, configurability, or writability of
25 /// existing properties, and prevents the values of existing properties from
26 /// being changed. In addition, freezing an object also prevents its prototype
27 /// from being changed.
28 Frozen,
29}
30
31impl IntegrityLevel {
32 /// Returns `true` if the integrity level is sealed.
33 pub fn is_sealed(&self) -> bool {
34 matches!(self, Self::Sealed)
35 }
36
37 /// Returns `true` if the integrity level is frozen.
38 pub fn is_frozen(&self) -> bool {
39 matches!(self, Self::Frozen)
40 }
41}
42
43impl JsObject {
44 /// Cehck if object is extensible.
45 ///
46 /// More information:
47 /// - [ECMAScript reference][spec]
48 ///
49 /// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
50 #[inline]
51 pub fn is_extensible(&self, context: &mut Context) -> JsResult<bool> {
52 // 1. Return ? O.[[IsExtensible]]().
53 self.__is_extensible__(context)
54 }
55
56 /// Get property from object or throw.
57 ///
58 /// More information:
59 /// - [ECMAScript reference][spec]
60 ///
61 /// [spec]: https://tc39.es/ecma262/#sec-get-o-p
62 #[inline]
63 pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
64 where
65 K: Into<PropertyKey>,
66 {
67 // 1. Assert: Type(O) is Object.
68 // 2. Assert: IsPropertyKey(P) is true.
69 // 3. Return ? O.[[Get]](P, O).
70 self.__get__(&key.into(), self.clone().into(), context)
71 }
72
73 /// set property of object or throw if bool flag is passed.
74 ///
75 /// More information:
76 /// - [ECMAScript reference][spec]
77 ///
78 /// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
79 #[inline]
80 pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool>
81 where
82 K: Into<PropertyKey>,
83 V: Into<JsValue>,
84 {
85 let key = key.into();
86 // 1. Assert: Type(O) is Object.
87 // 2. Assert: IsPropertyKey(P) is true.
88 // 3. Assert: Type(Throw) is Boolean.
89 // 4. Let success be ? O.[[Set]](P, V, O).
90 let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?;
91 // 5. If success is false and Throw is true, throw a TypeError exception.
92 if !success && throw {
93 return Err(
94 context.construct_type_error(format!("cannot set non-writable property: {}", key))
95 );
96 }
97 // 6. Return success.
98 Ok(success)
99 }
100
101 /// Create data property
102 ///
103 /// More information:
104 /// - [ECMAScript reference][spec]
105 ///
106 /// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
107 pub fn create_data_property<K, V>(
108 &self,
109 key: K,
110 value: V,
111 context: &mut Context,
112 ) -> JsResult<bool>
113 where
114 K: Into<PropertyKey>,
115 V: Into<JsValue>,
116 {
117 // 1. Assert: Type(O) is Object.
118 // 2. Assert: IsPropertyKey(P) is true.
119 // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
120 let new_desc = PropertyDescriptor::builder()
121 .value(value)
122 .writable(true)
123 .enumerable(true)
124 .configurable(true);
125 // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
126 self.__define_own_property__(key.into(), new_desc.into(), context)
127 }
128
129 // todo: CreateMethodProperty
130
131 /// Create data property or throw
132 ///
133 /// More information:
134 /// - [ECMAScript reference][spec]
135 ///
136 /// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
137 pub fn create_data_property_or_throw<K, V>(
138 &self,
139 key: K,
140 value: V,
141 context: &mut Context,
142 ) -> JsResult<bool>
143 where
144 K: Into<PropertyKey>,
145 V: Into<JsValue>,
146 {
147 let key = key.into();
148 // 1. Assert: Type(O) is Object.
149 // 2. Assert: IsPropertyKey(P) is true.
150 // 3. Let success be ? CreateDataProperty(O, P, V).
151 let success = self.create_data_property(key.clone(), value, context)?;
152 // 4. If success is false, throw a TypeError exception.
153 if !success {
154 return Err(context.construct_type_error(format!("cannot redefine property: {}", key)));
155 }
156 // 5. Return success.
157 Ok(success)
158 }
159
160 /// Define property or throw.
161 ///
162 /// More information:
163 /// - [ECMAScript reference][spec]
164 ///
165 /// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
166 #[inline]
167 pub fn define_property_or_throw<K, P>(
168 &self,
169 key: K,
170 desc: P,
171 context: &mut Context,
172 ) -> JsResult<bool>
173 where
174 K: Into<PropertyKey>,
175 P: Into<PropertyDescriptor>,
176 {
177 let key = key.into();
178 // 1. Assert: Type(O) is Object.
179 // 2. Assert: IsPropertyKey(P) is true.
180 // 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
181 let success = self.__define_own_property__(key.clone(), desc.into(), context)?;
182 // 4. If success is false, throw a TypeError exception.
183 if !success {
184 return Err(context.construct_type_error(format!("cannot redefine property: {}", key)));
185 }
186 // 5. Return success.
187 Ok(success)
188 }
189
190 /// Defines the property or throws a `TypeError` if the operation fails.
191 ///
192 /// More information:
193 /// - [EcmaScript reference][spec]
194 ///
195 /// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
196 #[inline]
197 pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
198 where
199 K: Into<PropertyKey>,
200 {
201 let key = key.into();
202 // 1. Assert: Type(O) is Object.
203 // 2. Assert: IsPropertyKey(P) is true.
204 // 3. Let success be ? O.[[Delete]](P).
205 let success = self.__delete__(&key, context)?;
206 // 4. If success is false, throw a TypeError exception.
207 if !success {
208 return Err(context.construct_type_error(format!("cannot delete property: {}", key)));
209 }
210 // 5. Return success.
211 Ok(success)
212 }
213
214 /// Retrieves value of specific property, when the value of the property is expected to be a function.
215 ///
216 /// More information:
217 /// - [EcmaScript reference][spec]
218 ///
219 /// [spec]: https://tc39.es/ecma262/#sec-getmethod
220 #[inline]
221 pub(crate) fn get_method<K>(&self, context: &mut Context, key: K) -> JsResult<Option<JsObject>>
222 where
223 K: Into<PropertyKey>,
224 {
225 // 1. Assert: IsPropertyKey(P) is true.
226 // 2. Let func be ? GetV(V, P).
227 let value = self.get(key, context)?;
228
229 // 3. If func is either undefined or null, return undefined.
230 if value.is_null_or_undefined() {
231 return Ok(None);
232 }
233
234 // 4. If IsCallable(func) is false, throw a TypeError exception.
235 // 5. Return func.
236 match value.as_object() {
237 Some(object) if object.is_callable() => Ok(Some(object)),
238 _ => Err(context
239 .construct_type_error("value returned for property of object is not a function")),
240 }
241 }
242
243 /// Check if object has property.
244 ///
245 /// More information:
246 /// - [ECMAScript reference][spec]
247 ///
248 /// [spec]: https://tc39.es/ecma262/#sec-hasproperty
249 #[inline]
250 pub fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
251 where
252 K: Into<PropertyKey>,
253 {
254 // 1. Assert: Type(O) is Object.
255 // 2. Assert: IsPropertyKey(P) is true.
256 // 3. Return ? O.[[HasProperty]](P).
257 self.__has_property__(&key.into(), context)
258 }
259
260 /// Check if object has an own property.
261 ///
262 /// More information:
263 /// - [ECMAScript reference][spec]
264 ///
265 /// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
266 #[inline]
267 pub fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
268 where
269 K: Into<PropertyKey>,
270 {
271 let key = key.into();
272 // 1. Assert: Type(O) is Object.
273 // 2. Assert: IsPropertyKey(P) is true.
274 // 3. Let desc be ? O.[[GetOwnProperty]](P).
275 let desc = self.__get_own_property__(&key, context)?;
276 // 4. If desc is undefined, return false.
277 // 5. Return true.
278 Ok(desc.is_some())
279 }
280
281 /// Call this object.
282 ///
283 /// # Panics
284 ///
285 /// Panics if the object is currently mutably borrowed.
286 // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
287 // <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
288 #[track_caller]
289 #[inline]
290 pub fn call(
291 &self,
292 this: &JsValue,
293 args: &[JsValue],
294 context: &mut Context,
295 ) -> JsResult<JsValue> {
296 self.call_construct(this, args, context, false)
297 }
298
299 /// Construct an instance of this object with the specified arguments.
300 ///
301 /// # Panics
302 ///
303 /// Panics if the object is currently mutably borrowed.
304 // <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
305 #[track_caller]
306 #[inline]
307 pub fn construct(
308 &self,
309 args: &[JsValue],
310 new_target: &JsValue,
311 context: &mut Context,
312 ) -> JsResult<JsValue> {
313 self.call_construct(new_target, args, context, true)
314 }
315
316 /// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
317 ///
318 /// More information:
319 /// - [ECMAScript reference][spec]
320 ///
321 /// [spec]: https://tc39.es/ecma262/#sec-setintegritylevel
322 #[inline]
323 pub fn set_integrity_level(
324 &self,
325 level: IntegrityLevel,
326 context: &mut Context,
327 ) -> JsResult<bool> {
328 // 1. Assert: Type(O) is Object.
329 // 2. Assert: level is either sealed or frozen.
330
331 // 3. Let status be ? O.[[PreventExtensions]]().
332 let status = self.__prevent_extensions__(context)?;
333 // 4. If status is false, return false.
334 if !status {
335 return Ok(false);
336 }
337
338 // 5. Let keys be ? O.[[OwnPropertyKeys]]().
339 let keys = self.__own_property_keys__(context)?;
340
341 match level {
342 // 6. If level is sealed, then
343 IntegrityLevel::Sealed => {
344 // a. For each element k of keys, do
345 for k in keys {
346 // i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
347 self.define_property_or_throw(
348 k,
349 PropertyDescriptor::builder().configurable(false).build(),
350 context,
351 )?;
352 }
353 }
354 // 7. Else,
355 // a. Assert: level is frozen.
356 IntegrityLevel::Frozen => {
357 // b. For each element k of keys, do
358 for k in keys {
359 // i. Let currentDesc be ? O.[[GetOwnProperty]](k).
360 let current_desc = self.__get_own_property__(&k, context)?;
361 // ii. If currentDesc is not undefined, then
362 if let Some(current_desc) = current_desc {
363 // 1. If IsAccessorDescriptor(currentDesc) is true, then
364 let desc = if current_desc.is_accessor_descriptor() {
365 // a. Let desc be the PropertyDescriptor { [[Configurable]]: false }.
366 PropertyDescriptor::builder().configurable(false).build()
367 // 2. Else,
368 } else {
369 // a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
370 PropertyDescriptor::builder()
371 .configurable(false)
372 .writable(false)
373 .build()
374 };
375 // 3. Perform ? DefinePropertyOrThrow(O, k, desc).
376 self.define_property_or_throw(k, desc, context)?;
377 }
378 }
379 }
380 }
381
382 // 8. Return true.
383 Ok(true)
384 }
385
386 /// Check if the object is [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
387 ///
388 /// More information:
389 /// - [ECMAScript reference][spec]
390 ///
391 /// [spec]: https://tc39.es/ecma262/#sec-testintegritylevel
392 #[inline]
393 pub fn test_integrity_level(
394 &self,
395 level: IntegrityLevel,
396 context: &mut Context,
397 ) -> JsResult<bool> {
398 // 1. Assert: Type(O) is Object.
399 // 2. Assert: level is either sealed or frozen.
400
401 // 3. Let extensible be ? IsExtensible(O).
402 let extensible = self.is_extensible(context)?;
403
404 // 4. If extensible is true, return false.
405 if extensible {
406 return Ok(false);
407 }
408
409 // 5. NOTE: If the object is extensible, none of its properties are examined.
410 // 6. Let keys be ? O.[[OwnPropertyKeys]]().
411 let keys = self.__own_property_keys__(context)?;
412
413 // 7. For each element k of keys, do
414 for k in keys {
415 // a. Let currentDesc be ? O.[[GetOwnProperty]](k).
416 let current_desc = self.__get_own_property__(&k, context)?;
417 // b. If currentDesc is not undefined, then
418 if let Some(current_desc) = current_desc {
419 // i. If currentDesc.[[Configurable]] is true, return false.
420 if current_desc.expect_configurable() {
421 return Ok(false);
422 }
423 // ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then
424 if level.is_frozen() && current_desc.is_data_descriptor() {
425 // 1. If currentDesc.[[Writable]] is true, return false.
426 if current_desc.expect_writable() {
427 return Ok(false);
428 }
429 }
430 }
431 }
432 // 8. Return true.
433 Ok(true)
434 }
435
436 #[inline]
437 pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> {
438 // 1. Assert: Type(obj) is Object.
439 // 2. Return ℝ(? ToLength(? Get(obj, "length"))).
440 self.get("length", context)?.to_length(context)
441 }
442
443 /// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
444 ///
445 /// The abstract operation SpeciesConstructor takes arguments O (an Object) and defaultConstructor (a constructor).
446 /// It is used to retrieve the constructor that should be used to create new objects that are derived from O.
447 /// defaultConstructor is the constructor to use if a constructor @@species property cannot be found starting from O.
448 ///
449 /// More information:
450 /// - [ECMAScript reference][spec]
451 ///
452 /// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
453 pub(crate) fn species_constructor(
454 &self,
455 default_constructor: JsValue,
456 context: &mut Context,
457 ) -> JsResult<JsValue> {
458 // 1. Assert: Type(O) is Object.
459
460 // 2. Let C be ? Get(O, "constructor").
461 let c = self.clone().get("constructor", context)?;
462
463 // 3. If C is undefined, return defaultConstructor.
464 if c.is_undefined() {
465 return Ok(default_constructor);
466 }
467
468 // 4. If Type(C) is not Object, throw a TypeError exception.
469 if !c.is_object() {
470 return context.throw_type_error("property 'constructor' is not an object");
471 }
472
473 // 5. Let S be ? Get(C, @@species).
474 let s = c.get_field(WellKnownSymbols::species(), context)?;
475
476 // 6. If S is either undefined or null, return defaultConstructor.
477 if s.is_null_or_undefined() {
478 return Ok(default_constructor);
479 }
480
481 // 7. If IsConstructor(S) is true, return S.
482 // 8. Throw a TypeError exception.
483 if let Some(obj) = s.as_object() {
484 if obj.is_constructable() {
485 Ok(s)
486 } else {
487 context.throw_type_error("property 'constructor' is not a constructor")
488 }
489 } else {
490 context.throw_type_error("property 'constructor' is not an object")
491 }
492 }
493
494 /// It is used to iterate over names of object's keys.
495 ///
496 /// More information:
497 /// - [EcmaScript reference][spec]
498 ///
499 /// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
500 pub(crate) fn enumerable_own_property_names(
501 &self,
502 kind: PropertyNameKind,
503 context: &mut Context,
504 ) -> JsResult<Vec<JsValue>> {
505 // 1. Assert: Type(O) is Object.
506 // 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
507 let own_keys = self.__own_property_keys__(context)?;
508 // 3. Let properties be a new empty List.
509 let mut properties = vec![];
510
511 // 4. For each element key of ownKeys, do
512 for key in own_keys {
513 // a. If Type(key) is String, then
514 let key_str = match &key {
515 PropertyKey::String(s) => Some(s.clone()),
516 PropertyKey::Index(i) => Some(i.to_string().into()),
517 _ => None,
518 };
519
520 if let Some(key_str) = key_str {
521 // i. Let desc be ? O.[[GetOwnProperty]](key).
522 let desc = self.__get_own_property__(&key, context)?;
523 // ii. If desc is not undefined and desc.[[Enumerable]] is true, then
524 if let Some(desc) = desc {
525 if desc.expect_enumerable() {
526 match kind {
527 // 1. If kind is key, append key to properties.
528 PropertyNameKind::Key => properties.push(key_str.into()),
529 // 2. Else,
530 // a. Let value be ? Get(O, key).
531 // b. If kind is value, append value to properties.
532 PropertyNameKind::Value => {
533 properties.push(self.get(key.clone(), context)?)
534 }
535 // c. Else,
536 // i. Assert: kind is key+value.
537 // ii. Let entry be ! CreateArrayFromList(« key, value »).
538 // iii. Append entry to properties.
539 PropertyNameKind::KeyAndValue => properties.push(
540 Array::create_array_from_list(
541 [key_str.into(), self.get(key.clone(), context)?],
542 context,
543 )
544 .into(),
545 ),
546 }
547 }
548 }
549 }
550 }
551
552 // 5. Return properties.
553 Ok(properties)
554 }
555
556 // todo: GetFunctionRealm
557
558 // todo: CopyDataProperties
559
560 // todo: PrivateElementFind
561
562 // todo: PrivateFieldAdd
563
564 // todo: PrivateMethodOrAccessorAdd
565
566 // todo: PrivateGet
567
568 // todo: PrivateSet
569
570 // todo: DefineField
571
572 // todo: InitializeInstanceElements
573}
574
575impl JsValue {
576 // todo: GetV
577
578 // todo: GetMethod
579
580 /// It is used to create List value whose elements are provided by the indexed properties of
581 /// self.
582 ///
583 /// More information:
584 /// - [EcmaScript reference][spec]
585 ///
586 /// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
587 pub(crate) fn create_list_from_array_like(
588 &self,
589 element_types: &[Type],
590 context: &mut Context,
591 ) -> JsResult<Vec<JsValue>> {
592 // 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
593 let types = if element_types.is_empty() {
594 &[
595 Type::Undefined,
596 Type::Null,
597 Type::Boolean,
598 Type::String,
599 Type::Symbol,
600 Type::Number,
601 Type::BigInt,
602 Type::Object,
603 ]
604 } else {
605 element_types
606 };
607
608 // 2. If Type(obj) is not Object, throw a TypeError exception.
609 let obj = self
610 .as_object()
611 .ok_or_else(|| context.construct_type_error("cannot create list from a primitive"))?;
612
613 // 3. Let len be ? LengthOfArrayLike(obj).
614 let len = obj.length_of_array_like(context)?;
615
616 // 4. Let list be a new empty List.
617 let mut list = Vec::with_capacity(len);
618
619 // 5. Let index be 0.
620 // 6. Repeat, while index < len,
621 for index in 0..len {
622 // a. Let indexName be ! ToString(𝔽(index)).
623 // b. Let next be ? Get(obj, indexName).
624 let next = obj.get(index, context)?;
625 // c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
626 if !types.contains(&next.get_type()) {
627 return Err(context.construct_type_error("bad type"));
628 }
629 // d. Append next as the last element of list.
630 list.push(next.clone());
631 // e. Set index to index + 1.
632 }
633
634 // 7. Return list.
635 Ok(list)
636 }
637}