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}