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}