Skip to main content

boa_engine/object/
operations.rs

1use super::internal_methods::{InternalMethodCallContext, InternalMethodPropertyContext};
2use crate::js_error;
3use crate::value::JsVariant;
4use crate::{
5    Context, JsExpect, JsResult, JsSymbol, JsValue,
6    builtins::{
7        Array, Proxy,
8        function::{BoundFunction, ClassFieldDefinition, OrdinaryFunction, set_function_name},
9    },
10    context::intrinsics::{StandardConstructor, StandardConstructors},
11    error::JsNativeError,
12    native_function::{NativeFunctionObject, native_function_call_with_args},
13    object::{CONSTRUCTOR, JsObject, PROTOTYPE, PrivateElement, PrivateName},
14    property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
15    realm::Realm,
16    string::StaticJsStrings,
17    value::Type,
18};
19
20/// Object integrity level.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum IntegrityLevel {
23    /// Sealed object integrity level.
24    ///
25    /// Preventing new properties from being added to it and marking all existing
26    /// properties as non-configurable. Values of present properties can still be
27    /// changed as long as they are writable.
28    Sealed,
29
30    /// Frozen object integrity level
31    ///
32    /// A frozen object can no longer be changed; freezing an object prevents new
33    /// properties from being added to it, existing properties from being removed,
34    /// prevents changing the enumerability, configurability, or writability of
35    /// existing properties, and prevents the values of existing properties from
36    /// being changed. In addition, freezing an object also prevents its prototype
37    /// from being changed.
38    Frozen,
39}
40
41impl IntegrityLevel {
42    /// Returns `true` if the integrity level is sealed.
43    #[must_use]
44    pub const fn is_sealed(&self) -> bool {
45        matches!(self, Self::Sealed)
46    }
47
48    /// Returns `true` if the integrity level is frozen.
49    #[must_use]
50    pub const fn is_frozen(&self) -> bool {
51        matches!(self, Self::Frozen)
52    }
53}
54
55impl JsObject {
56    /// Check if object is extensible.
57    ///
58    /// More information:
59    ///  - [ECMAScript reference][spec]
60    ///
61    /// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
62    #[inline]
63    pub fn is_extensible(&self, context: &mut Context) -> JsResult<bool> {
64        // 1. Return ? O.[[IsExtensible]]().
65        self.__is_extensible__(context)
66    }
67
68    /// Get property from object or throw.
69    ///
70    /// More information:
71    ///  - [ECMAScript reference][spec]
72    ///
73    /// [spec]: https://tc39.es/ecma262/#sec-get-o-p
74    pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
75    where
76        K: Into<PropertyKey>,
77    {
78        // 1. Assert: Type(O) is Object.
79        // 2. Assert: IsPropertyKey(P) is true.
80        // 3. Return ? O.[[Get]](P, O).
81        self.__get__(
82            &key.into(),
83            self.clone().into(),
84            &mut InternalMethodPropertyContext::new(context),
85        )
86    }
87
88    /// set property of object or throw if bool flag is passed.
89    ///
90    /// More information:
91    ///  - [ECMAScript reference][spec]
92    ///
93    /// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
94    pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool>
95    where
96        K: Into<PropertyKey>,
97        V: Into<JsValue>,
98    {
99        let key = key.into();
100        // 1. Assert: Type(O) is Object.
101        // 2. Assert: IsPropertyKey(P) is true.
102        // 3. Assert: Type(Throw) is Boolean.
103        // 4. Let success be ? O.[[Set]](P, V, O).
104        let success = self.__set__(
105            key.clone(),
106            value.into(),
107            self.clone().into(),
108            &mut InternalMethodPropertyContext::new(context),
109        )?;
110        // 5. If success is false and Throw is true, throw a TypeError exception.
111        if !success && throw {
112            return Err(JsNativeError::typ()
113                .with_message(format!("cannot set non-writable property: {key}"))
114                .into());
115        }
116        // 6. Return success.
117        Ok(success)
118    }
119
120    /// Create data property
121    ///
122    /// More information:
123    ///  - [ECMAScript reference][spec]
124    ///
125    /// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
126    pub fn create_data_property<K, V>(
127        &self,
128        key: K,
129        value: V,
130        context: &mut Context,
131    ) -> JsResult<bool>
132    where
133        K: Into<PropertyKey>,
134        V: Into<JsValue>,
135    {
136        let key = key.into();
137        let value = value.into();
138        if let PropertyKey::Index(index) = &key
139            && Array::try_append_dense(self, u64::from(index.get()), &value, context)
140        {
141            return Ok(true);
142        }
143        // 1. Assert: Type(O) is Object.
144        // 2. Assert: IsPropertyKey(P) is true.
145        // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
146        let new_desc = PropertyDescriptor::builder()
147            .value(value)
148            .writable(true)
149            .enumerable(true)
150            .configurable(true);
151        // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
152        self.__define_own_property__(
153            &key,
154            new_desc.into(),
155            &mut InternalMethodPropertyContext::new(context),
156        )
157    }
158
159    /// Create data property
160    ///
161    /// More information:
162    ///  - [ECMAScript reference][spec]
163    ///
164    /// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
165    pub(crate) fn create_data_property_with_slot<K, V>(
166        &self,
167        key: K,
168        value: V,
169        context: &mut InternalMethodPropertyContext<'_>,
170    ) -> JsResult<bool>
171    where
172        K: Into<PropertyKey>,
173        V: Into<JsValue>,
174    {
175        // 1. Assert: Type(O) is Object.
176        // 2. Assert: IsPropertyKey(P) is true.
177        // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
178        let new_desc = PropertyDescriptor::builder()
179            .value(value)
180            .writable(true)
181            .enumerable(true)
182            .configurable(true);
183        // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
184        self.__define_own_property__(&key.into(), new_desc.into(), context)
185    }
186
187    /// `10.2.8 DefineMethodProperty ( homeObject, key, closure, enumerable )`
188    ///
189    /// Defines a method property on an object with the specified attributes.
190    ///
191    /// More information:
192    ///  - [ECMAScript reference][spec]
193    ///
194    /// [spec]: https://tc39.es/ecma262/#sec-definemethodproperty
195    pub(crate) fn define_method_property<K, V>(
196        &self,
197        key: K,
198        value: V,
199        context: &mut InternalMethodPropertyContext<'_>,
200    ) -> JsResult<()>
201    where
202        K: Into<PropertyKey>,
203        V: Into<JsValue>,
204    {
205        // 1. Assert: homeObject is an ordinary, extensible object with no non-configurable properties.
206        // 2. If key is a Private Name, then
207        //    a. Return PrivateElement { [[Key]]: key, [[Kind]]: method, [[Value]]: closure }.
208        // 3. Else,
209        //    a. Let desc be the PropertyDescriptor { [[Value]]: closure, [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true }.
210        let new_desc = PropertyDescriptor::builder()
211            .value(value)
212            .writable(true)
213            .enumerable(false)
214            .configurable(true);
215
216        //    b. Perform ! DefinePropertyOrThrow(homeObject, key, desc).
217        self.__define_own_property__(&key.into(), new_desc.into(), context)?;
218
219        //    c. Return unused.
220        Ok(())
221    }
222
223    /// Create data property or throw
224    ///
225    /// More information:
226    ///  - [ECMAScript reference][spec]
227    ///
228    /// [spec]: https://tc39.es/ecma262/#sec-createdatapropertyorthrow
229    pub fn create_data_property_or_throw<K, V>(
230        &self,
231        key: K,
232        value: V,
233        context: &mut Context,
234    ) -> JsResult<bool>
235    where
236        K: Into<PropertyKey>,
237        V: Into<JsValue>,
238    {
239        let key = key.into();
240        // 1. Assert: Type(O) is Object.
241        // 2. Assert: IsPropertyKey(P) is true.
242        // 3. Let success be ? CreateDataProperty(O, P, V).
243        let success = self.create_data_property(key.clone(), value, context)?;
244        // 4. If success is false, throw a TypeError exception.
245        if !success {
246            return Err(JsNativeError::typ()
247                .with_message(format!("cannot redefine property: {key}"))
248                .into());
249        }
250        // 5. Return success.
251        Ok(success)
252    }
253
254    /// Create non-enumerable data property or throw
255    ///
256    /// More information:
257    ///  - [ECMAScript reference][spec]
258    ///
259    /// [spec]: https://tc39.es/ecma262/#sec-createnonenumerabledatapropertyinfallibly
260    pub(crate) fn create_non_enumerable_data_property_or_throw<K, V>(
261        &self,
262        key: K,
263        value: V,
264        context: &mut Context,
265    ) where
266        K: Into<PropertyKey>,
267        V: Into<JsValue>,
268    {
269        // 1. Assert: O is an ordinary, extensible object with no non-configurable properties.
270
271        // 2. Let newDesc be the PropertyDescriptor {
272        //    [[Value]]: V,
273        //    [[Writable]]: true,
274        //    [[Enumerable]]: false,
275        //    [[Configurable]]: true
276        //  }.
277        let new_desc = PropertyDescriptorBuilder::new()
278            .value(value)
279            .writable(true)
280            .enumerable(false)
281            .configurable(true)
282            .build();
283
284        // 3. Perform ! DefinePropertyOrThrow(O, P, newDesc).
285        self.define_property_or_throw(key, new_desc, context)
286            .expect("should not fail according to spec");
287
288        // 4. Return unused.
289    }
290
291    /// Define property or throw.
292    ///
293    /// More information:
294    ///  - [ECMAScript reference][spec]
295    ///
296    /// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
297    pub fn define_property_or_throw<K, P>(
298        &self,
299        key: K,
300        desc: P,
301        context: &mut Context,
302    ) -> JsResult<bool>
303    where
304        K: Into<PropertyKey>,
305        P: Into<PropertyDescriptor>,
306    {
307        let key = key.into();
308        // 1. Assert: Type(O) is Object.
309        // 2. Assert: IsPropertyKey(P) is true.
310        // 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
311        let success = self.__define_own_property__(
312            &key,
313            desc.into(),
314            &mut InternalMethodPropertyContext::new(context),
315        )?;
316        // 4. If success is false, throw a TypeError exception.
317        if !success {
318            return Err(JsNativeError::typ()
319                .with_message(format!("cannot redefine property: {key}"))
320                .into());
321        }
322        // 5. Return success.
323        Ok(success)
324    }
325
326    /// Defines the property or throws a `TypeError` if the operation fails.
327    ///
328    /// More information:
329    /// - [ECMAScript reference][spec]
330    ///
331    /// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
332    pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
333    where
334        K: Into<PropertyKey>,
335    {
336        let key = key.into();
337        // 1. Assert: Type(O) is Object.
338        // 2. Assert: IsPropertyKey(P) is true.
339        // 3. Let success be ? O.[[Delete]](P).
340        let success = self.__delete__(&key, &mut InternalMethodPropertyContext::new(context))?;
341        // 4. If success is false, throw a TypeError exception.
342        if !success {
343            return Err(JsNativeError::typ()
344                .with_message(format!("cannot delete non-configurable property: {key}"))
345                .into());
346        }
347        // 5. Return success.
348        Ok(success)
349    }
350
351    /// Check if object has property.
352    ///
353    /// More information:
354    ///  - [ECMAScript reference][spec]
355    ///
356    /// [spec]: https://tc39.es/ecma262/#sec-hasproperty
357    pub fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
358    where
359        K: Into<PropertyKey>,
360    {
361        // 1. Assert: Type(O) is Object.
362        // 2. Assert: IsPropertyKey(P) is true.
363        // 3. Return ? O.[[HasProperty]](P).
364
365        self.__has_property__(
366            &key.into(),
367            &mut InternalMethodPropertyContext::new(context),
368        )
369    }
370
371    /// Abstract optimization operation.
372    ///
373    /// Check if an object has a property and get it if it exists.
374    /// This operation combines the abstract operations `HasProperty` and `Get`.
375    ///
376    /// More information:
377    ///  - [ECMAScript reference HasProperty][spec0]
378    ///  - [ECMAScript reference Get][spec1]
379    ///
380    /// [spec0]: https://tc39.es/ecma262/#sec-hasproperty
381    /// [spec1]: https://tc39.es/ecma262/#sec-get-o-p
382    pub(crate) fn try_get<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsValue>>
383    where
384        K: Into<PropertyKey>,
385    {
386        self.__try_get__(
387            &key.into(),
388            self.clone().into(),
389            &mut InternalMethodPropertyContext::new(context),
390        )
391    }
392
393    /// Check if object has an own property.
394    ///
395    /// More information:
396    ///  - [ECMAScript reference][spec]
397    ///
398    /// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
399    pub fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
400    where
401        K: Into<PropertyKey>,
402    {
403        let key = key.into();
404        // 1. Assert: Type(O) is Object.
405        // 2. Assert: IsPropertyKey(P) is true.
406        // 3. Let desc be ? O.[[GetOwnProperty]](P).
407        let desc =
408            self.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
409        // 4. If desc is undefined, return false.
410        // 5. Return true.
411        Ok(desc.is_some())
412    }
413
414    /// Get all the keys of the properties of this object.
415    ///
416    /// More information:
417    ///  - [ECMAScript reference][spec]
418    ///
419    /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
420    pub fn own_property_keys(&self, context: &mut Context) -> JsResult<Vec<PropertyKey>> {
421        self.__own_property_keys__(context)
422    }
423
424    /// `Call ( F, V [ , argumentsList ] )`
425    ///
426    /// # Panics
427    ///
428    /// Panics if the object is currently mutably borrowed.
429    ///
430    /// More information:
431    ///  - [ECMAScript reference][spec]
432    ///
433    /// [spec]: https://tc39.es/ecma262/#sec-call
434    #[track_caller]
435    #[inline]
436    pub fn call(
437        &self,
438        this: &JsValue,
439        args: &[JsValue],
440        context: &mut Context,
441    ) -> JsResult<JsValue> {
442        // SKIP: 1. If argumentsList is not present, set argumentsList to a new empty List.
443        // SKIP: 2. If IsCallable(F) is false, throw a TypeError exception.
444        // NOTE(HalidOdat): For object's that are not callable we implement a special __call__ internal method
445        //                  that throws on call.
446
447        if self.is::<NativeFunctionObject>() {
448            return native_function_call_with_args(
449                self,
450                this,
451                args,
452                &mut InternalMethodCallContext::new(context),
453            );
454        }
455
456        let argument_count = args.len();
457        context
458            .vm
459            .stack
460            .calling_convention_push_call(this.clone(), self.clone().into(), args);
461
462        // 3. Return ? F.[[Call]](V, argumentsList).
463        let frame_index = context.vm.frames.len();
464        if self.__call_resolve(argument_count, context)? {
465            return Ok(context.vm.stack.pop());
466        }
467
468        if frame_index + 1 == context.vm.frames.len() {
469            context.vm.frame_mut().set_exit_early(true);
470        } else {
471            context.vm.frames[frame_index + 1].set_exit_early(true);
472        }
473
474        context.vm.host_call_depth += 1;
475        let result = context.run().consume();
476        context.vm.host_call_depth = context.vm.host_call_depth.saturating_sub(1);
477
478        context.vm.pop_frame().js_expect("frame must exist")?;
479
480        result
481    }
482
483    /// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
484    ///
485    /// Construct an instance of this object with the specified arguments.
486    ///
487    /// # Panics
488    ///
489    /// Panics if the object is currently mutably borrowed.
490    ///
491    /// More information:
492    ///  - [ECMAScript reference][spec]
493    ///
494    /// [spec]: https://tc39.es/ecma262/#sec-construct
495    #[track_caller]
496    #[inline]
497    pub fn construct(
498        &self,
499        args: &[JsValue],
500        new_target: Option<&Self>,
501        context: &mut Context,
502    ) -> JsResult<Self> {
503        // 1. If newTarget is not present, set newTarget to F.
504        let new_target = new_target.unwrap_or(self);
505
506        context.vm.stack.push(JsValue::undefined());
507        context.vm.stack.push(self.clone()); // func
508        let argument_count = args.len();
509        context.vm.stack.calling_convention_push_arguments(args);
510        context.vm.stack.push(new_target.clone());
511
512        // 2. If argumentsList is not present, set argumentsList to a new empty List.
513        // 3. Return ? F.[[Construct]](argumentsList, newTarget).
514        let frame_index = context.vm.frames.len();
515
516        if self.__construct__(argument_count).resolve(context)? {
517            let result = context.vm.stack.pop();
518            return Ok(result
519                .as_object()
520                .js_expect("construct value should be an object")?
521                .clone());
522        }
523
524        if frame_index + 1 == context.vm.frames.len() {
525            context.vm.frame_mut().set_exit_early(true);
526        } else {
527            context.vm.frames[frame_index + 1].set_exit_early(true);
528        }
529
530        context.vm.host_call_depth += 1;
531        let result = context.run().consume();
532        context.vm.host_call_depth = context.vm.host_call_depth.saturating_sub(1);
533
534        context.vm.pop_frame().js_expect("frame must exist")?;
535
536        Ok(result?
537            .as_object()
538            .js_expect("should be an object")?
539            .clone())
540    }
541
542    /// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
543    ///
544    /// More information:
545    ///  - [ECMAScript reference][spec]
546    ///
547    /// [spec]: https://tc39.es/ecma262/#sec-setintegritylevel
548    pub fn set_integrity_level(
549        &self,
550        level: IntegrityLevel,
551        context: &mut Context,
552    ) -> JsResult<bool> {
553        // 1. Assert: Type(O) is Object.
554        // 2. Assert: level is either sealed or frozen.
555
556        // 3. Let status be ? O.[[PreventExtensions]]().
557        let status =
558            self.__prevent_extensions__(&mut InternalMethodPropertyContext::new(context))?;
559        // 4. If status is false, return false.
560        if !status {
561            return Ok(false);
562        }
563
564        // 5. Let keys be ? O.[[OwnPropertyKeys]]().
565        let keys = self.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
566
567        match level {
568            // 6. If level is sealed, then
569            IntegrityLevel::Sealed => {
570                // a. For each element k of keys, do
571                for k in keys {
572                    // i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
573                    self.define_property_or_throw(
574                        k,
575                        PropertyDescriptor::builder().configurable(false).build(),
576                        context,
577                    )?;
578                }
579            }
580            // 7. Else,
581            //     a. Assert: level is frozen.
582            IntegrityLevel::Frozen => {
583                // b. For each element k of keys, do
584                for k in keys {
585                    // i. Let currentDesc be ? O.[[GetOwnProperty]](k).
586                    let current_desc = self.__get_own_property__(
587                        &k,
588                        &mut InternalMethodPropertyContext::new(context),
589                    )?;
590                    // ii. If currentDesc is not undefined, then
591                    if let Some(current_desc) = current_desc {
592                        // 1. If IsAccessorDescriptor(currentDesc) is true, then
593                        let desc = if current_desc.is_accessor_descriptor() {
594                            // a. Let desc be the PropertyDescriptor { [[Configurable]]: false }.
595                            PropertyDescriptor::builder().configurable(false).build()
596                        // 2. Else,
597                        } else {
598                            // a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
599                            PropertyDescriptor::builder()
600                                .configurable(false)
601                                .writable(false)
602                                .build()
603                        };
604                        // 3. Perform ? DefinePropertyOrThrow(O, k, desc).
605                        self.define_property_or_throw(k, desc, context)?;
606                    }
607                }
608            }
609        }
610
611        // 8. Return true.
612        Ok(true)
613    }
614
615    /// Check if the object is [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
616    ///
617    /// More information:
618    ///  - [ECMAScript reference][spec]
619    ///
620    /// [spec]: https://tc39.es/ecma262/#sec-testintegritylevel
621    pub fn test_integrity_level(
622        &self,
623        level: IntegrityLevel,
624        context: &mut Context,
625    ) -> JsResult<bool> {
626        // 1. Assert: Type(O) is Object.
627        // 2. Assert: level is either sealed or frozen.
628
629        // 3. Let extensible be ? IsExtensible(O).
630        let extensible = self.is_extensible(context)?;
631
632        // 4. If extensible is true, return false.
633        if extensible {
634            return Ok(false);
635        }
636
637        // 5. NOTE: If the object is extensible, none of its properties are examined.
638        // 6. Let keys be ? O.[[OwnPropertyKeys]]().
639        let keys = self.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
640
641        // 7. For each element k of keys, do
642        for k in keys {
643            // a. Let currentDesc be ? O.[[GetOwnProperty]](k).
644            let current_desc =
645                self.__get_own_property__(&k, &mut InternalMethodPropertyContext::new(context))?;
646            // b. If currentDesc is not undefined, then
647            if let Some(current_desc) = current_desc {
648                // i. If currentDesc.[[Configurable]] is true, return false.
649                if current_desc.expect_configurable() {
650                    return Ok(false);
651                }
652                // ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then
653                if level.is_frozen() && current_desc.is_data_descriptor() {
654                    // 1. If currentDesc.[[Writable]] is true, return false.
655                    if current_desc.expect_writable() {
656                        return Ok(false);
657                    }
658                }
659            }
660        }
661        // 8. Return true.
662        Ok(true)
663    }
664
665    /// Abstract operation [`LengthOfArrayLike ( obj )`][spec].
666    ///
667    /// Returns the value of the "length" property of an array-like object.
668    ///
669    /// [spec]: https://tc39.es/ecma262/#sec-lengthofarraylike
670    pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<u64> {
671        // 1. Assert: Type(obj) is Object.
672
673        // NOTE: This is an optimization, most of the cases that `LengthOfArrayLike` will be called
674        //       is for arrays. The "length" property of an array is stored in the first index.
675        if self.is_array() {
676            let borrowed_object = self.borrow();
677            // NOTE: using `to_u32` instead of `to_length` is an optimization,
678            //       since arrays are limited to [0, 2^32 - 1] range.
679            return borrowed_object.properties().storage[0]
680                .to_u32(context)
681                .map(u64::from);
682        }
683
684        // 2. Return ℝ(? ToLength(? Get(obj, "length"))).
685        self.get(StaticJsStrings::LENGTH, context)?
686            .to_length(context)
687    }
688
689    /// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
690    ///
691    /// The abstract operation `SpeciesConstructor` takes arguments `O` (an Object) and
692    /// `defaultConstructor` (a constructor). It is used to retrieve the constructor that should be
693    /// used to create new objects that are derived from `O`. `defaultConstructor` is the
694    /// constructor to use if a constructor `@@species` property cannot be found starting from `O`.
695    ///
696    /// More information:
697    ///  - [ECMAScript reference][spec]
698    ///
699    /// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
700    pub(crate) fn species_constructor<F>(
701        &self,
702        default_constructor: F,
703        context: &mut Context,
704    ) -> JsResult<Self>
705    where
706        F: FnOnce(&StandardConstructors) -> &StandardConstructor,
707    {
708        // 1. Assert: Type(O) is Object.
709
710        // 2. Let C be ? Get(O, "constructor").
711        let c = self.get(CONSTRUCTOR, context)?;
712
713        // 3. If C is undefined, return defaultConstructor.
714        if c.is_undefined() {
715            return Ok(default_constructor(context.intrinsics().constructors()).constructor());
716        }
717
718        // 4. If Type(C) is not Object, throw a TypeError exception.
719        let c = c.as_object().ok_or_else(|| {
720            JsNativeError::typ().with_message("property 'constructor' is not an object")
721        })?;
722
723        // 5. Let S be ? Get(C, @@species).
724        let s = c.get(JsSymbol::species(), context)?;
725
726        // 6. If S is either undefined or null, return defaultConstructor.
727        if s.is_null_or_undefined() {
728            return Ok(default_constructor(context.intrinsics().constructors()).constructor());
729        }
730
731        // 7. If IsConstructor(S) is true, return S.
732        if let Some(s) = s.as_constructor() {
733            return Ok(s.clone());
734        }
735
736        // 8. Throw a TypeError exception.
737        Err(JsNativeError::typ()
738            .with_message("property 'constructor' is not a constructor")
739            .into())
740    }
741
742    /// It is used to iterate over names of object's keys.
743    ///
744    /// More information:
745    /// - [ECMAScript reference][spec]
746    ///
747    /// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
748    pub(crate) fn enumerable_own_property_names(
749        &self,
750        kind: PropertyNameKind,
751        context: &mut Context,
752    ) -> JsResult<Vec<JsValue>> {
753        // 1. Assert: Type(O) is Object.
754        // 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
755        let own_keys =
756            self.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
757        // 3. Let properties be a new empty List.
758        let mut properties = Vec::with_capacity(own_keys.len());
759
760        // 4. For each element key of ownKeys, do
761        for key in own_keys {
762            // a. If Type(key) is String, then
763            let key_str = match &key {
764                PropertyKey::String(s) => Some(s.clone()),
765                PropertyKey::Index(i) => Some(i.get().to_string().into()),
766                PropertyKey::Symbol(_) => None,
767            };
768
769            if let Some(key_str) = key_str {
770                // i. Let desc be ? O.[[GetOwnProperty]](key).
771                let desc = self
772                    .__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
773                // ii. If desc is not undefined and desc.[[Enumerable]] is true, then
774                if let Some(desc) = desc
775                    && desc.expect_enumerable()
776                {
777                    match kind {
778                        // 1. If kind is key, append key to properties.
779                        PropertyNameKind::Key => properties.push(key_str.into()),
780                        // 2. Else,
781                        // a. Let value be ? Get(O, key).
782                        // b. If kind is value, append value to properties.
783                        PropertyNameKind::Value => {
784                            properties.push(self.get(key.clone(), context)?);
785                        }
786                        // c. Else,
787                        // i. Assert: kind is key+value.
788                        // ii. Let entry be ! CreateArrayFromList(« key, value »).
789                        // iii. Append entry to properties.
790                        PropertyNameKind::KeyAndValue => properties.push(
791                            Array::create_array_from_list(
792                                [key_str.into(), self.get(key.clone(), context)?],
793                                context,
794                            )
795                            .into(),
796                        ),
797                    }
798                }
799            }
800        }
801
802        // 5. Return properties.
803        Ok(properties)
804    }
805
806    /// Abstract operation `GetMethod ( V, P )`
807    ///
808    /// Retrieves the value of a specific property, when the value of the property is expected to be a function.
809    ///
810    /// More information:
811    /// - [ECMAScript reference][spec]
812    ///
813    /// [spec]: https://tc39.es/ecma262/#sec-getmethod
814    pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<Self>>
815    where
816        K: Into<PropertyKey>,
817    {
818        // Note: The spec specifies this function for JsValue.
819        // It is implemented for JsObject for convenience.
820
821        // 1. Assert: IsPropertyKey(P) is true.
822        // 2. Let func be ? GetV(V, P).
823
824        match self
825            .__get__(
826                &key.into(),
827                self.clone().into(),
828                &mut InternalMethodPropertyContext::new(context),
829            )?
830            .variant()
831        {
832            // 3. If func is either undefined or null, return undefined.
833            JsVariant::Undefined | JsVariant::Null => Ok(None),
834            // 5. Return func.
835            JsVariant::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())),
836            // 4. If IsCallable(func) is false, throw a TypeError exception.
837            _ => Err(JsNativeError::typ()
838                .with_message("value returned for property of object is not a function")
839                .into()),
840        }
841    }
842
843    /// Abstract operation `IsArray ( argument )`
844    ///
845    /// Check if a value is an array.
846    ///
847    /// More information:
848    ///  - [ECMAScript reference][spec]
849    ///
850    /// [spec]: https://tc39.es/ecma262/#sec-isarray
851    pub(crate) fn is_array_abstract(&self) -> JsResult<bool> {
852        // Note: The spec specifies this function for JsValue.
853        // It is implemented for JsObject for convenience.
854
855        // 2. If argument is an Array exotic object, return true.
856        if self.is_array() {
857            return Ok(true);
858        }
859
860        // 3. If argument is a Proxy exotic object, then
861        if let Some(proxy) = self.downcast_ref::<Proxy>() {
862            // a. If argument.[[ProxyHandler]] is null, throw a TypeError exception.
863            // b. Let target be argument.[[ProxyTarget]].
864            let (target, _) = proxy.try_data()?;
865
866            // c. Return ? IsArray(target).
867            return target.is_array_abstract();
868        }
869
870        // 4. Return false.
871        Ok(false)
872    }
873
874    /// Abstract operation [`GetFunctionRealm`][spec].
875    ///
876    /// [spec]: https://tc39.es/ecma262/#sec-getfunctionrealm
877    pub(crate) fn get_function_realm(&self, context: &mut Context) -> JsResult<Realm> {
878        if let Some(fun) = self.downcast_ref::<OrdinaryFunction>() {
879            return Ok(fun.realm().clone());
880        }
881
882        if let Some(f) = self.downcast_ref::<NativeFunctionObject>() {
883            return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone()));
884        }
885
886        if let Some(bound) = self.downcast_ref::<BoundFunction>() {
887            let fun = bound.target_function().clone();
888            return fun.get_function_realm(context);
889        }
890
891        if let Some(proxy) = self.downcast_ref::<Proxy>() {
892            let (fun, _) = proxy.try_data()?;
893            return fun.get_function_realm(context);
894        }
895
896        Ok(context.realm().clone())
897    }
898
899    /// `7.3.26 CopyDataProperties ( target, source, excludedItems )`
900    ///
901    /// More information:
902    ///  - [ECMAScript reference][spec]
903    ///
904    /// [spec]: https://tc39.es/ecma262/#sec-copydataproperties
905    pub fn copy_data_properties<K>(
906        &self,
907        source: &JsValue,
908        excluded_keys: Vec<K>,
909        context: &mut Context,
910    ) -> JsResult<()>
911    where
912        K: Into<PropertyKey>,
913    {
914        let context = &mut InternalMethodPropertyContext::new(context);
915
916        // 1. Assert: Type(target) is Object.
917        // 2. Assert: excludedItems is a List of property keys.
918        // 3. If source is undefined or null, return target.
919        if source.is_null_or_undefined() {
920            return Ok(());
921        }
922
923        // 4. Let from be ! ToObject(source).
924        let from = source
925            .to_object(context)
926            .expect("function ToObject should never complete abruptly here");
927
928        // 5. Let keys be ? from.[[OwnPropertyKeys]]().
929        // 6. For each element nextKey of keys, do
930        let excluded_keys: Vec<PropertyKey> = excluded_keys.into_iter().map(Into::into).collect();
931        for key in from.__own_property_keys__(context)? {
932            // a. Let excluded be false.
933            let mut excluded = false;
934
935            // b. For each element e of excludedItems, do
936            for e in &excluded_keys {
937                // i. If SameValue(e, nextKey) is true, then
938                if *e == key {
939                    // 1. Set excluded to true.
940                    excluded = true;
941                    break;
942                }
943            }
944            // c. If excluded is false, then
945            if !excluded {
946                // i. Let desc be ? from.[[GetOwnProperty]](nextKey).
947                let desc = from.__get_own_property__(&key, context)?;
948
949                // ii. If desc is not undefined and desc.[[Enumerable]] is true, then
950                if let Some(desc) = desc
951                    && let Some(enumerable) = desc.enumerable()
952                    && enumerable
953                {
954                    // 1. Let propValue be ? Get(from, nextKey).
955                    let prop_value = from.__get__(&key, from.clone().into(), context)?;
956
957                    // 2. Perform ! CreateDataPropertyOrThrow(target, nextKey, propValue).
958                    self.create_data_property_or_throw(key, prop_value, context)
959                        .expect("CreateDataPropertyOrThrow should never complete abruptly here");
960                }
961            }
962        }
963
964        // 7. Return target.
965        Ok(())
966    }
967
968    /// Abstract operation `PrivateElementFind ( O, P )`
969    ///
970    /// Get the private element from an object.
971    ///
972    /// More information:
973    ///  - [ECMAScript specification][spec]
974    ///
975    /// [spec]: https://tc39.es/ecma262/#sec-privateelementfind
976    #[allow(clippy::similar_names)]
977    pub(crate) fn private_element_find(
978        &self,
979        name: &PrivateName,
980        is_getter: bool,
981        is_setter: bool,
982    ) -> Option<PrivateElement> {
983        // 1. If O.[[PrivateElements]] contains a PrivateElement whose [[Key]] is P, then
984        for (key, value) in &self.borrow().private_elements {
985            if key == name {
986                // a. Let entry be that PrivateElement.
987                // b. Return entry.
988                if let PrivateElement::Accessor { getter, setter } = value {
989                    if getter.is_some() && is_getter || setter.is_some() && is_setter {
990                        return Some(value.clone());
991                    }
992                } else {
993                    return Some(value.clone());
994                }
995            }
996        }
997
998        // 2. Return empty.
999        None
1000    }
1001
1002    /// Abstract operation `PrivateFieldAdd ( O, P, value )`
1003    ///
1004    /// Add private field to an object.
1005    ///
1006    /// More information:
1007    ///  - [ECMAScript specification][spec]
1008    ///
1009    /// [spec]: https://tc39.es/ecma262/#sec-privatefieldadd
1010    pub(crate) fn private_field_add(
1011        &self,
1012        name: &PrivateName,
1013        value: JsValue,
1014        context: &mut Context,
1015    ) -> JsResult<()> {
1016        // 1. If the host is a web browser, then
1017        //    a. Perform ? HostEnsureCanAddPrivateElement(O).
1018        context
1019            .host_hooks()
1020            .ensure_can_add_private_element(self, context)?;
1021
1022        // 2. If ? IsExtensible(O) is false, throw a TypeError exception.
1023        // NOTE: From <https://tc39.es/proposal-nonextensible-applies-to-private/#sec-privatefieldadd>
1024        if !self.is_extensible(context)? {
1025            return Err(js_error!(
1026                TypeError: "cannot add private field to non-extensible class instance"
1027            ));
1028        }
1029
1030        // 3. Let entry be PrivateElementFind(O, P).
1031        let entry = self.private_element_find(name, false, false);
1032
1033        // 4. If entry is not empty, throw a TypeError exception.
1034        if entry.is_some() {
1035            return Err(js_error!(TypeError: "private field already exists on prototype"));
1036        }
1037
1038        // 5. Append PrivateElement { [[Key]]: P, [[Kind]]: field, [[Value]]: value } to O.[[PrivateElements]].
1039        self.borrow_mut()
1040            .private_elements
1041            .push((name.clone(), PrivateElement::Field(value)));
1042
1043        // 5. Return unused.
1044        Ok(())
1045    }
1046
1047    /// Abstract operation `PrivateMethodOrAccessorAdd ( O, method )`
1048    ///
1049    /// Add private method or accessor to an object.
1050    ///
1051    /// More information:
1052    ///  - [ECMAScript specification][spec]
1053    ///
1054    /// [spec]: https://tc39.es/ecma262/#sec-privatemethodoraccessoradd
1055    pub(crate) fn private_method_or_accessor_add(
1056        &self,
1057        name: &PrivateName,
1058        method: &PrivateElement,
1059        context: &mut Context,
1060    ) -> JsResult<()> {
1061        // 1. Assert: method.[[Kind]] is either method or accessor.
1062        assert!(matches!(
1063            method,
1064            PrivateElement::Method(_) | PrivateElement::Accessor { .. }
1065        ));
1066
1067        let (getter, setter) = if let PrivateElement::Accessor { getter, setter } = method {
1068            (getter.is_some(), setter.is_some())
1069        } else {
1070            (false, false)
1071        };
1072
1073        // 2. If the host is a web browser, then
1074        // a. Perform ? HostEnsureCanAddPrivateElement(O).
1075        context
1076            .host_hooks()
1077            .ensure_can_add_private_element(self, context)?;
1078
1079        // 3. If ? IsExtensible(O) is false, throw a TypeError exception.
1080        // NOTE: From <https://tc39.es/proposal-nonextensible-applies-to-private/#sec-privatemethodoraccessoradd>
1081        if !self.is_extensible(context)? {
1082            return if getter || setter {
1083                Err(js_error!(
1084                    TypeError: "cannot add private accessor to non-extensible class instance"
1085                ))
1086            } else {
1087                Err(js_error!(
1088                    TypeError: "cannot add private method to non-extensible class instance"
1089                ))
1090            };
1091        }
1092
1093        // 3. Let entry be PrivateElementFind(O, method.[[Key]]).
1094        let entry = self.private_element_find(name, getter, setter);
1095
1096        // 4. If entry is not empty, throw a TypeError exception.
1097        if entry.is_some() {
1098            return if getter || setter {
1099                Err(js_error!(
1100                    TypeError: "private accessor already exists on class instance"
1101                ))
1102            } else {
1103                Err(js_error!(
1104                    TypeError: "private method already exists on class instance"
1105                ))
1106            };
1107        }
1108
1109        // 5. Append method to O.[[PrivateElements]].
1110        self.borrow_mut()
1111            .append_private_element(name.clone(), method.clone());
1112
1113        // 6. Return unused.
1114        Ok(())
1115    }
1116
1117    /// Abstract operation `PrivateGet ( O, P )`
1118    ///
1119    /// Get the value of a private element.
1120    ///
1121    /// More information:
1122    ///  - [ECMAScript specification][spec]
1123    ///
1124    /// [spec]: https://tc39.es/ecma262/#sec-privateget
1125    pub(crate) fn private_get(
1126        &self,
1127        name: &PrivateName,
1128        context: &mut Context,
1129    ) -> JsResult<JsValue> {
1130        // 1. Let entry be PrivateElementFind(O, P).
1131        let entry = self.private_element_find(name, true, true);
1132
1133        match &entry {
1134            // 2. If entry is empty, throw a TypeError exception.
1135            None => Err(JsNativeError::typ()
1136                .with_message("Private element does not exist on object")
1137                .into()),
1138
1139            // 3. If entry.[[Kind]] is field or method, then
1140            // a. Return entry.[[Value]].
1141            Some(PrivateElement::Field(value)) => Ok(value.clone()),
1142            Some(PrivateElement::Method(value)) => Ok(value.clone().into()),
1143
1144            // 4. Assert: entry.[[Kind]] is accessor.
1145            Some(PrivateElement::Accessor { getter, .. }) => {
1146                // 5. If entry.[[Get]] is undefined, throw a TypeError exception.
1147                // 6. Let getter be entry.[[Get]].
1148                let getter = getter.as_ref().ok_or_else(|| {
1149                    JsNativeError::typ()
1150                        .with_message("private property was defined without a getter")
1151                })?;
1152
1153                // 7. Return ? Call(getter, O).
1154                getter.call(&self.clone().into(), &[], context)
1155            }
1156        }
1157    }
1158
1159    /// Abstract operation `PrivateSet ( O, P, value )`
1160    ///
1161    /// Set the value of a private element.
1162    ///
1163    /// More information:
1164    ///  - [ECMAScript specification][spec]
1165    ///
1166    /// [spec]: https://tc39.es/ecma262/#sec-privateset
1167    pub(crate) fn private_set(
1168        &self,
1169        name: &PrivateName,
1170        value: JsValue,
1171        context: &mut Context,
1172    ) -> JsResult<()> {
1173        // 1. Let entry be PrivateElementFind(O, P).
1174        // Note: This function is inlined here for mutable access.
1175        let mut object_mut = self.borrow_mut();
1176        let entry = object_mut
1177            .private_elements
1178            .iter_mut()
1179            .find_map(|(key, value)| if key == name { Some(value) } else { None });
1180
1181        match entry {
1182            // 2. If entry is empty, throw a TypeError exception.
1183            None => {
1184                return Err(JsNativeError::typ()
1185                    .with_message("Private element does not exist on object")
1186                    .into());
1187            }
1188
1189            // 3. If entry.[[Kind]] is field, then
1190            // a. Set entry.[[Value]] to value.
1191            Some(PrivateElement::Field(field)) => {
1192                *field = value;
1193            }
1194
1195            // 4. Else if entry.[[Kind]] is method, then
1196            // a. Throw a TypeError exception.
1197            Some(PrivateElement::Method(_)) => {
1198                return Err(JsNativeError::typ()
1199                    .with_message("private method is not writable")
1200                    .into());
1201            }
1202
1203            // 5. Else,
1204            Some(PrivateElement::Accessor { setter, .. }) => {
1205                // a. Assert: entry.[[Kind]] is accessor.
1206                // b. If entry.[[Set]] is undefined, throw a TypeError exception.
1207                // c. Let setter be entry.[[Set]].
1208                let setter = setter.clone().ok_or_else(|| {
1209                    JsNativeError::typ()
1210                        .with_message("private property was defined without a setter")
1211                })?;
1212
1213                // d. Perform ? Call(setter, O, « value »).
1214                drop(object_mut);
1215                setter.call(&self.clone().into(), &[value], context)?;
1216            }
1217        }
1218
1219        // 6. Return unused.
1220        Ok(())
1221    }
1222
1223    /// Abstract operation `DefineField ( receiver, fieldRecord )`
1224    ///
1225    /// Define a field on an object.
1226    ///
1227    /// More information:
1228    ///  - [ECMAScript specification][spec]
1229    ///
1230    /// [spec]: https://tc39.es/ecma262/#sec-definefield
1231    pub(crate) fn define_field(
1232        &self,
1233        field_record: &ClassFieldDefinition,
1234        context: &mut Context,
1235    ) -> JsResult<()> {
1236        // 2. Let initializer be fieldRecord.[[Initializer]].
1237        let initializer = match field_record {
1238            ClassFieldDefinition::Public(_, function, _)
1239            | ClassFieldDefinition::Private(_, function) => function,
1240        };
1241
1242        // 3. If initializer is not empty, then
1243        // a. Let initValue be ? Call(initializer, receiver).
1244        // 4. Else, let initValue be undefined.
1245        let init_value = initializer.call(&self.clone().into(), &[], context)?;
1246
1247        match field_record {
1248            // 1. Let fieldName be fieldRecord.[[Name]].
1249            // 5. If fieldName is a Private Name, then
1250            ClassFieldDefinition::Private(field_name, _) => {
1251                // a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
1252                self.private_field_add(field_name, init_value, context)?;
1253            }
1254            // 1. Let fieldName be fieldRecord.[[Name]].
1255            // 6. Else,
1256            ClassFieldDefinition::Public(field_name, _, function_name) => {
1257                if let Some(function_name) = function_name {
1258                    set_function_name(
1259                        &init_value
1260                            .as_object()
1261                            .js_expect("init value must be a function object")?,
1262                        function_name,
1263                        None,
1264                        context,
1265                    )?;
1266                }
1267
1268                // a. Assert: IsPropertyKey(fieldName) is true.
1269                // b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName, initValue).
1270                self.create_data_property_or_throw(field_name.clone(), init_value, context)?;
1271            }
1272        }
1273
1274        // 7. Return unused.
1275        Ok(())
1276    }
1277
1278    /// Abstract operation `InitializeInstanceElements ( O, constructor )`
1279    ///
1280    /// Add private methods and fields from a class constructor to an object.
1281    ///
1282    /// More information:
1283    ///  - [ECMAScript specification][spec]
1284    ///
1285    /// [spec]: https://tc39.es/ecma262/#sec-initializeinstanceelements
1286    pub(crate) fn initialize_instance_elements(
1287        &self,
1288        constructor: &Self,
1289        context: &mut Context,
1290    ) -> JsResult<()> {
1291        let constructor_function = constructor
1292            .downcast_ref::<OrdinaryFunction>()
1293            .js_expect("class constructor must be function object")?;
1294
1295        // 1. Let methods be the value of constructor.[[PrivateMethods]].
1296        // 2. For each PrivateElement method of methods, do
1297        for (name, method) in constructor_function.get_private_methods() {
1298            // a. Perform ? PrivateMethodOrAccessorAdd(O, method).
1299            self.private_method_or_accessor_add(name, method, context)?;
1300        }
1301
1302        // 3. Let fields be the value of constructor.[[Fields]].
1303        // 4. For each element fieldRecord of fields, do
1304        for field_record in constructor_function.get_fields() {
1305            // a. Perform ? DefineField(O, fieldRecord).
1306            self.define_field(field_record, context)?;
1307        }
1308
1309        // 5. Return unused.
1310        Ok(())
1311    }
1312
1313    /// Abstract operation `Invoke ( V, P [ , argumentsList ] )`
1314    ///
1315    /// Calls a method property of an ECMAScript object.
1316    ///
1317    /// Equivalent to the [`JsValue::invoke`] method, but specialized for objects.
1318    ///
1319    /// More information:
1320    /// - [ECMAScript reference][spec]
1321    ///
1322    /// [spec]: https://tc39.es/ecma262/#sec-invoke
1323    pub(crate) fn invoke<K>(
1324        &self,
1325        key: K,
1326        args: &[JsValue],
1327        context: &mut Context,
1328    ) -> JsResult<JsValue>
1329    where
1330        K: Into<PropertyKey>,
1331    {
1332        let this_value: JsValue = self.clone().into();
1333
1334        // 1. If argumentsList is not present, set argumentsList to a new empty List.
1335        // 2. Let func be ? GetV(V, P).
1336        let func = self.__get__(
1337            &key.into(),
1338            this_value.clone(),
1339            &mut InternalMethodPropertyContext::new(context),
1340        )?;
1341
1342        // 3. Return ? Call(func, V, argumentsList)
1343        func.call(&this_value, args, context)
1344    }
1345}
1346
1347impl JsValue {
1348    /// Abstract operation `GetV ( V, P )`.
1349    ///
1350    /// Retrieves the value of a specific property of an ECMAScript language value. If the value is
1351    /// not an object, the property lookup is performed using a wrapper object appropriate for the
1352    /// type of the value.
1353    ///
1354    /// More information:
1355    /// - [ECMAScript reference][spec]
1356    ///
1357    /// [spec]: https://tc39.es/ecma262/#sec-getv
1358    pub(crate) fn get_v<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
1359    where
1360        K: Into<PropertyKey>,
1361    {
1362        // 1. Let O be ? ToObject(V).
1363        let o = self.to_object(context)?;
1364
1365        // 2. Return ? O.[[Get]](P, V).
1366
1367        o.__get__(
1368            &key.into(),
1369            self.clone(),
1370            &mut InternalMethodPropertyContext::new(context),
1371        )
1372    }
1373
1374    /// Abstract operation `GetMethod ( V, P )`
1375    ///
1376    /// Retrieves the value of a specific property, when the value of the property is expected to be a function.
1377    ///
1378    /// More information:
1379    /// - [ECMAScript reference][spec]
1380    ///
1381    /// [spec]: https://tc39.es/ecma262/#sec-getmethod
1382    pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
1383    where
1384        K: Into<PropertyKey>,
1385    {
1386        // Note: The spec specifies this function for JsValue.
1387        // The main part of the function is implemented for JsObject.
1388        // 1. Let func be ? GetV(V, P).
1389        match self.get_v(key, context)?.variant() {
1390            // 3. If func is either undefined or null, return undefined.
1391            JsVariant::Undefined | JsVariant::Null => Ok(None),
1392            // 5. Return func.
1393            JsVariant::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())),
1394            // 4. If IsCallable(func) is false, throw a TypeError exception.
1395            _ => Err(JsNativeError::typ()
1396                .with_message("value returned for property of object is not a function")
1397                .into()),
1398        }
1399    }
1400
1401    /// It is used to create List value whose elements are provided by the indexed properties of
1402    /// self.
1403    ///
1404    /// More information:
1405    /// - [ECMAScript reference][spec]
1406    ///
1407    /// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
1408    pub(crate) fn create_list_from_array_like(
1409        &self,
1410        element_types: &[Type],
1411        context: &mut Context,
1412    ) -> JsResult<Vec<Self>> {
1413        // 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
1414        let types = if element_types.is_empty() {
1415            &[
1416                Type::Undefined,
1417                Type::Null,
1418                Type::Boolean,
1419                Type::String,
1420                Type::Symbol,
1421                Type::Number,
1422                Type::BigInt,
1423                Type::Object,
1424            ]
1425        } else {
1426            element_types
1427        };
1428
1429        // 2. If Type(obj) is not Object, throw a TypeError exception.
1430        let obj = self.as_object().ok_or_else(|| {
1431            JsNativeError::typ().with_message("cannot create list from a primitive")
1432        })?;
1433
1434        // 3. Let len be ? LengthOfArrayLike(obj).
1435        let len = obj.length_of_array_like(context)?;
1436
1437        // 4. Let list be a new empty List.
1438        let mut list = Vec::with_capacity(len as usize);
1439
1440        // 5. Let index be 0.
1441        // 6. Repeat, while index < len,
1442        for index in 0..len {
1443            // a. Let indexName be ! ToString(𝔽(index)).
1444            // b. Let next be ? Get(obj, indexName).
1445            let next = obj.get(index, context)?;
1446            // c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
1447            if !types.contains(&next.get_type()) {
1448                return Err(JsNativeError::typ().with_message("bad type").into());
1449            }
1450            // d. Append next as the last element of list.
1451            list.push(next.clone());
1452            // e. Set index to index + 1.
1453        }
1454
1455        // 7. Return list.
1456        Ok(list)
1457    }
1458
1459    /// Abstract operation [`Call ( F, V [ , argumentsList ] )`][call].
1460    ///
1461    /// Calls this value if the value is a callable object.
1462    ///
1463    /// # Note
1464    ///
1465    /// It is almost always better to try to obtain a callable object first with [`JsValue::as_callable`],
1466    /// then calling [`JsObject::call`], since that allows reusing the unwrapped function for other
1467    /// operations. This method is only an utility method for when the spec directly uses `Call`
1468    /// without using the value as a proper object.
1469    ///
1470    /// [call]: https://tc39.es/ecma262/#sec-call
1471    #[inline]
1472    #[cfg_attr(feature = "native-backtrace", track_caller)]
1473    pub(crate) fn call(&self, this: &Self, args: &[Self], context: &mut Context) -> JsResult<Self> {
1474        let Some(object) = self.as_object() else {
1475            return Err(JsNativeError::typ()
1476                .with_message(format!(
1477                    "value with type `{}` is not callable",
1478                    self.type_of()
1479                ))
1480                .into());
1481        };
1482
1483        object.call(this, args, context)
1484    }
1485
1486    /// Abstract operation `( V, P [ , argumentsList ] )`
1487    ///
1488    /// Calls a method property of an ECMAScript language value.
1489    ///
1490    /// More information:
1491    /// - [ECMAScript reference][spec]
1492    ///
1493    /// [spec]: https://tc39.es/ecma262/#sec-invoke
1494    #[cfg_attr(feature = "native-backtrace", track_caller)]
1495    pub(crate) fn invoke<K>(&self, key: K, args: &[Self], context: &mut Context) -> JsResult<Self>
1496    where
1497        K: Into<PropertyKey>,
1498    {
1499        // 1. If argumentsList is not present, set argumentsList to a new empty List.
1500        // 2. Let func be ? GetV(V, P).
1501        let func = self.get_v(key, context)?;
1502
1503        // 3. Return ? Call(func, V, argumentsList)
1504        func.call(self, args, context)
1505    }
1506
1507    /// Abstract operation `OrdinaryHasInstance ( C, O )`
1508    ///
1509    /// More information:
1510    /// - [ECMAScript reference][spec]
1511    ///
1512    /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
1513    pub fn ordinary_has_instance(
1514        function: &Self,
1515        object: &Self,
1516        context: &mut Context,
1517    ) -> JsResult<bool> {
1518        // 1. If IsCallable(C) is false, return false.
1519        let Some(function) = function.as_callable() else {
1520            return Ok(false);
1521        };
1522
1523        // 2. If C has a [[BoundTargetFunction]] internal slot, then
1524        if let Some(bound_function) = function.downcast_ref::<BoundFunction>() {
1525            // a. Let BC be C.[[BoundTargetFunction]].
1526            // b. Return ? InstanceofOperator(O, BC).
1527            return object.instance_of(&bound_function.target_function().clone().into(), context);
1528        }
1529
1530        let Some(mut object) = object.as_object() else {
1531            // 3. If Type(O) is not Object, return false.
1532            return Ok(false);
1533        };
1534
1535        // 4. Let P be ? Get(C, "prototype").
1536        let prototype = function.get(PROTOTYPE, context)?;
1537
1538        // 5. If Type(P) is not Object, throw a TypeError exception.
1539        let prototype = prototype.as_object().ok_or_else(|| {
1540            JsNativeError::typ()
1541                .with_message("function has non-object prototype in instanceof check")
1542        })?;
1543
1544        // 6. Repeat,
1545        loop {
1546            // a. Set O to ? O.[[GetPrototypeOf]]().
1547            object = match object
1548                .__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))?
1549            {
1550                Some(obj) => obj,
1551                // b. If O is null, return false.
1552                None => return Ok(false),
1553            };
1554
1555            // c. If SameValue(P, O) is true, return true.
1556            if JsObject::equals(&object, &prototype) {
1557                return Ok(true);
1558            }
1559        }
1560    }
1561}