boa_engine/builtins/object/mod.rs
1//! Boa's implementation of ECMAScript's global `Object` object.
2//!
3//! The `Object` class represents one of ECMAScript's data types.
4//!
5//! It is used to store various keyed collections and more complex entities.
6//! Objects can be created using the `Object()` constructor or the
7//! object initializer / literal syntax.
8//!
9//! More information:
10//! - [ECMAScript reference][spec]
11//! - [MDN documentation][mdn]
12//!
13//! [spec]: https://tc39.es/ecma262/#sec-objects
14//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
15
16use super::{
17 Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, error::Error,
18};
19use crate::builtins::function::arguments::{MappedArguments, UnmappedArguments};
20use crate::value::JsVariant;
21use crate::{
22 Context, JsArgs, JsData, JsExpect, JsResult, JsString,
23 builtins::{BuiltInObject, iterable::IteratorHint, map},
24 context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
25 js_error, js_string,
26 native_function::NativeFunction,
27 object::{
28 FunctionObjectBuilder, IntegrityLevel, JsObject,
29 internal_methods::{InternalMethodPropertyContext, get_prototype_from_constructor},
30 },
31 property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
32 realm::Realm,
33 string::StaticJsStrings,
34 symbol::JsSymbol,
35 value::JsValue,
36};
37use boa_gc::{Finalize, Trace};
38use boa_macros::js_str;
39use tap::{Conv, Pipe};
40
41pub(crate) mod for_in_iterator;
42#[cfg(test)]
43mod tests;
44
45/// An ordinary Javascript `Object`.
46#[derive(Debug, Default, Clone, Copy, Trace, Finalize, JsData)]
47#[boa_gc(empty_trace)]
48pub struct OrdinaryObject;
49
50impl IntrinsicObject for OrdinaryObject {
51 fn init(realm: &Realm) {
52 let legacy_proto_getter = BuiltInBuilder::callable(realm, Self::legacy_proto_getter)
53 .name(js_string!("get __proto__"))
54 .build();
55
56 let legacy_setter_proto = BuiltInBuilder::callable(realm, Self::legacy_proto_setter)
57 .name(js_string!("set __proto__"))
58 .length(1)
59 .build();
60
61 BuiltInBuilder::from_standard_constructor::<Self>(realm)
62 .inherits(None)
63 .accessor(
64 js_string!("__proto__"),
65 Some(legacy_proto_getter),
66 Some(legacy_setter_proto),
67 Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
68 )
69 .method(Self::has_own_property, js_string!("hasOwnProperty"), 1)
70 .method(
71 Self::property_is_enumerable,
72 js_string!("propertyIsEnumerable"),
73 1,
74 )
75 .method(Self::to_string, js_string!("toString"), 0)
76 .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
77 .method(Self::value_of, js_string!("valueOf"), 0)
78 .method(Self::is_prototype_of, js_string!("isPrototypeOf"), 1)
79 .method(
80 Self::legacy_define_getter,
81 js_string!("__defineGetter__"),
82 2,
83 )
84 .method(
85 Self::legacy_define_setter,
86 js_string!("__defineSetter__"),
87 2,
88 )
89 .method(
90 Self::legacy_lookup_getter,
91 js_string!("__lookupGetter__"),
92 1,
93 )
94 .method(
95 Self::legacy_lookup_setter,
96 js_string!("__lookupSetter__"),
97 1,
98 )
99 .static_method(Self::create, js_string!("create"), 2)
100 .static_method(Self::set_prototype_of, js_string!("setPrototypeOf"), 2)
101 .static_method(Self::get_prototype_of, js_string!("getPrototypeOf"), 1)
102 .static_method(Self::define_property, js_string!("defineProperty"), 3)
103 .static_method(Self::define_properties, js_string!("defineProperties"), 2)
104 .static_method(Self::assign, js_string!("assign"), 2)
105 .static_method(Self::is, js_string!("is"), 2)
106 .static_method(Self::keys, js_string!("keys"), 1)
107 .static_method(Self::values, js_string!("values"), 1)
108 .static_method(Self::entries, js_string!("entries"), 1)
109 .static_method(Self::seal, js_string!("seal"), 1)
110 .static_method(Self::is_sealed, js_string!("isSealed"), 1)
111 .static_method(Self::freeze, js_string!("freeze"), 1)
112 .static_method(Self::is_frozen, js_string!("isFrozen"), 1)
113 .static_method(Self::prevent_extensions, js_string!("preventExtensions"), 1)
114 .static_method(Self::is_extensible, js_string!("isExtensible"), 1)
115 .static_method(
116 Self::get_own_property_descriptor,
117 js_string!("getOwnPropertyDescriptor"),
118 2,
119 )
120 .static_method(
121 Self::get_own_property_descriptors,
122 js_string!("getOwnPropertyDescriptors"),
123 1,
124 )
125 .static_method(
126 Self::get_own_property_names,
127 js_string!("getOwnPropertyNames"),
128 1,
129 )
130 .static_method(
131 Self::get_own_property_symbols,
132 js_string!("getOwnPropertySymbols"),
133 1,
134 )
135 .static_method(Self::has_own, js_string!("hasOwn"), 2)
136 .static_method(Self::from_entries, js_string!("fromEntries"), 1)
137 .static_method(Self::group_by, js_string!("groupBy"), 2)
138 .build();
139 }
140
141 fn get(intrinsics: &Intrinsics) -> JsObject {
142 Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
143 }
144}
145
146impl BuiltInObject for OrdinaryObject {
147 const NAME: JsString = StaticJsStrings::OBJECT;
148}
149
150impl BuiltInConstructor for OrdinaryObject {
151 const CONSTRUCTOR_ARGUMENTS: usize = 1;
152 const PROTOTYPE_STORAGE_SLOTS: usize = 12;
153 const CONSTRUCTOR_STORAGE_SLOTS: usize = 23;
154
155 const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
156 StandardConstructors::object;
157
158 fn constructor(
159 new_target: &JsValue,
160 args: &[JsValue],
161 context: &mut Context,
162 ) -> JsResult<JsValue> {
163 // 1. If NewTarget is neither undefined nor the active function object, then
164 if !new_target.is_undefined()
165 && new_target
166 != &context
167 .active_function_object()
168 .unwrap_or_else(|| context.intrinsics().constructors().object().constructor())
169 .into()
170 {
171 // a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
172 let prototype =
173 get_prototype_from_constructor(new_target, StandardConstructors::object, context)?;
174 let object = JsObject::from_proto_and_data_with_shared_shape(
175 context.root_shape(),
176 prototype,
177 OrdinaryObject,
178 );
179 return Ok(object.into());
180 }
181
182 let value = args.get_or_undefined(0);
183
184 // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%).
185 if value.is_null_or_undefined() {
186 Ok(JsObject::with_object_proto(context.intrinsics()).into())
187 } else {
188 // 3. Return ! ToObject(value).
189 value.to_object(context).map(JsValue::from)
190 }
191 }
192}
193
194impl OrdinaryObject {
195 /// `get Object.prototype.__proto__`
196 ///
197 /// The `__proto__` getter function exposes the value of the
198 /// internal `[[Prototype]]` of an object.
199 ///
200 /// More information:
201 /// - [ECMAScript reference][spec]
202 /// - [MDN documentation][mdn]
203 ///
204 /// [spec]: https://tc39.es/ecma262/#sec-get-object.prototype.__proto__
205 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
206 pub fn legacy_proto_getter(
207 this: &JsValue,
208 _: &[JsValue],
209 context: &mut Context,
210 ) -> JsResult<JsValue> {
211 // 1. Let O be ? ToObject(this value).
212 let obj = this.to_object(context)?;
213
214 // 2. Return ? O.[[GetPrototypeOf]]().
215 let proto = obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))?;
216
217 Ok(proto.map_or(JsValue::null(), JsValue::new))
218 }
219
220 /// `set Object.prototype.__proto__`
221 ///
222 /// The `__proto__` setter allows the `[[Prototype]]` of
223 /// an object to be mutated.
224 ///
225 /// More information:
226 /// - [ECMAScript reference][spec]
227 /// - [MDN documentation][mdn]
228 ///
229 /// [spec]: https://tc39.es/ecma262/#sec-set-object.prototype.__proto__
230 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
231 pub fn legacy_proto_setter(
232 this: &JsValue,
233 args: &[JsValue],
234 context: &mut Context,
235 ) -> JsResult<JsValue> {
236 // 1. Let O be ? RequireObjectCoercible(this value).
237 let this = this.require_object_coercible()?;
238
239 // 2. If Type(proto) is neither Object nor Null, return undefined.
240 let proto = match args.get_or_undefined(0).variant() {
241 JsVariant::Object(proto) => Some(proto.clone()),
242 JsVariant::Null => None,
243 _ => return Ok(JsValue::undefined()),
244 };
245
246 // 3. If Type(O) is not Object, return undefined.
247 let JsVariant::Object(object) = this.variant() else {
248 return Ok(JsValue::undefined());
249 };
250
251 // 4. Let status be ? O.[[SetPrototypeOf]](proto).
252 let status =
253 object.__set_prototype_of__(proto, &mut InternalMethodPropertyContext::new(context))?;
254
255 // 5. If status is false, throw a TypeError exception.
256 if !status {
257 return Err(js_error!(TypeError: "__proto__ called on null or undefined"));
258 }
259
260 // 6. Return undefined.
261 Ok(JsValue::undefined())
262 }
263
264 /// `Object.prototype.__defineGetter__(prop, func)`
265 ///
266 /// Binds an object's property to a function to be called when that property is looked up.
267 ///
268 /// More information:
269 /// - [ECMAScript reference][spec]
270 /// - [MDN documentation][mdn]
271 ///
272 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__defineGetter__
273 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__
274 pub fn legacy_define_getter(
275 this: &JsValue,
276 args: &[JsValue],
277 context: &mut Context,
278 ) -> JsResult<JsValue> {
279 let getter = args.get_or_undefined(1);
280
281 // 1. Let O be ? ToObject(this value).
282 let obj = this.to_object(context)?;
283
284 // 2. If IsCallable(getter) is false, throw a TypeError exception.
285 if !getter.is_callable() {
286 return Err(js_error!(TypeError:
287 "Object.prototype.__defineGetter__: expected 'getter' to be a Function object",
288 ));
289 }
290
291 // 3. Let desc be PropertyDescriptor { [[Get]]: getter, [[Enumerable]]: true, [[Configurable]]: true }.
292 let desc = PropertyDescriptor::builder()
293 .get(getter.clone())
294 .enumerable(true)
295 .configurable(true);
296
297 // 4. Let key be ? ToPropertyKey(P).
298 let key = args.get_or_undefined(0).to_property_key(context)?;
299
300 // 5. Perform ? DefinePropertyOrThrow(O, key, desc).
301 obj.define_property_or_throw(key, desc, context)?;
302
303 // 6. Return undefined.
304 Ok(JsValue::undefined())
305 }
306
307 /// `Object.prototype.__defineSetter__(prop, func)`
308 ///
309 /// Binds an object's property to a function to be called when an attempt is made to set that property.
310 ///
311 /// More information:
312 /// - [ECMAScript reference][spec]
313 /// - [MDN documentation][mdn]
314 ///
315 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__defineSetter__
316 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineSetter__
317 pub fn legacy_define_setter(
318 this: &JsValue,
319 args: &[JsValue],
320 context: &mut Context,
321 ) -> JsResult<JsValue> {
322 let setter = args.get_or_undefined(1);
323
324 // 1. Let O be ? ToObject(this value).
325 let obj = this.to_object(context)?;
326
327 // 2. If IsCallable(setter) is false, throw a TypeError exception.
328 if !setter.is_callable() {
329 return Err(js_error!(TypeError:
330 "Object.prototype.__defineSetter__: expected 'setter' to be a Function object",
331 ));
332 }
333
334 // 3. Let desc be PropertyDescriptor { [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: true }.
335 let desc = PropertyDescriptor::builder()
336 .set(setter.clone())
337 .enumerable(true)
338 .configurable(true);
339
340 // 4. Let key be ? ToPropertyKey(P).
341 let key = args.get_or_undefined(0).to_property_key(context)?;
342
343 // 5. Perform ? DefinePropertyOrThrow(O, key, desc).
344 obj.define_property_or_throw(key, desc, context)?;
345
346 // 6. Return undefined.
347 Ok(JsValue::undefined())
348 }
349
350 /// `Object.prototype.__lookupGetter__(prop)`
351 ///
352 /// Returns the function bound as a getter to the specified property.
353 ///
354 /// More information:
355 /// - [ECMAScript reference][spec]
356 /// - [MDN documentation][mdn]
357 ///
358 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__lookupGetter__
359 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupGetter__
360 pub fn legacy_lookup_getter(
361 this: &JsValue,
362 args: &[JsValue],
363 context: &mut Context,
364 ) -> JsResult<JsValue> {
365 // 1. Let O be ? ToObject(this value).
366 let mut obj = this.to_object(context)?;
367
368 // 2. Let key be ? ToPropertyKey(P).
369 let key = args.get_or_undefined(0).to_property_key(context)?;
370
371 // 3. Repeat
372 loop {
373 // a. Let desc be ? O.[[GetOwnProperty]](key).
374
375 let desc =
376 obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
377
378 // b. If desc is not undefined, then
379 if let Some(current_desc) = desc {
380 // i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
381 return if current_desc.is_accessor_descriptor() {
382 Ok(current_desc.expect_get().clone())
383 } else {
384 // ii. Return undefined.
385 Ok(JsValue::undefined())
386 };
387 }
388 match obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))? {
389 // c. Set O to ? O.[[GetPrototypeOf]]().
390 Some(o) => obj = o,
391 // d. If O is null, return undefined.
392 None => return Ok(JsValue::undefined()),
393 }
394 }
395 }
396 /// `Object.prototype.__lookupSetter__(prop)`
397 ///
398 /// Returns the function bound as a getter to the specified property.
399 ///
400 /// More information:
401 /// - [ECMAScript reference][spec]
402 /// - [MDN documentation][mdn]
403 ///
404 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__lookupSetter__
405 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupSetter__
406 pub fn legacy_lookup_setter(
407 this: &JsValue,
408 args: &[JsValue],
409 context: &mut Context,
410 ) -> JsResult<JsValue> {
411 // 1. Let O be ? ToObject(this value).
412 let mut obj = this.to_object(context)?;
413
414 // 2. Let key be ? ToPropertyKey(P).
415 let key = args.get_or_undefined(0).to_property_key(context)?;
416
417 // 3. Repeat
418 loop {
419 // a. Let desc be ? O.[[GetOwnProperty]](key).
420
421 let desc =
422 obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
423
424 // b. If desc is not undefined, then
425 if let Some(current_desc) = desc {
426 // i. If IsAccessorDescriptor(desc) is true, return desc.[[Set]].
427 return if current_desc.is_accessor_descriptor() {
428 Ok(current_desc.expect_set().clone())
429 } else {
430 // ii. Return undefined.
431 Ok(JsValue::undefined())
432 };
433 }
434 match obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))? {
435 // c. Set O to ? O.[[GetPrototypeOf]]().
436 Some(o) => obj = o,
437 // d. If O is null, return undefined.
438 None => return Ok(JsValue::undefined()),
439 }
440 }
441 }
442
443 /// `Object.create( proto, [propertiesObject] )`
444 ///
445 /// Creates a new object from the provided prototype.
446 ///
447 /// More information:
448 /// - [ECMAScript reference][spec]
449 /// - [MDN documentation][mdn]
450 ///
451 /// [spec]: https://tc39.es/ecma262/#sec-object.create
452 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
453 pub fn create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
454 let prototype = args.get_or_undefined(0);
455 let properties = args.get_or_undefined(1);
456
457 let obj = match prototype.variant() {
458 JsVariant::Object(_) | JsVariant::Null => {
459 JsObject::from_proto_and_data_with_shared_shape(
460 context.root_shape(),
461 prototype.as_object(),
462 OrdinaryObject,
463 )
464 .upcast()
465 }
466 _ => {
467 return Err(js_error!(TypeError:
468 "Object.create: expected 'proto' to be an Object or null, got `{}`",
469 prototype.type_of()
470 ));
471 }
472 };
473
474 if !properties.is_undefined() {
475 object_define_properties(&obj, properties, context)?;
476 return Ok(obj.into());
477 }
478
479 Ok(obj.into())
480 }
481
482 /// `Object.getOwnPropertyDescriptor( object, property )`
483 ///
484 /// Returns an object describing the configuration of a specific property on a given object.
485 ///
486 /// More information:
487 /// - [ECMAScript reference][spec]
488 /// - [MDN documentation][mdn]
489 ///
490 /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
491 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
492 pub fn get_own_property_descriptor(
493 _: &JsValue,
494 args: &[JsValue],
495 context: &mut Context,
496 ) -> JsResult<JsValue> {
497 // 1. Let obj be ? ToObject(O).
498 let obj = args.get_or_undefined(0).to_object(context)?;
499
500 // 2. Let key be ? ToPropertyKey(P).
501 let key = args.get_or_undefined(1).to_property_key(context)?;
502
503 // 3. Let desc be ? obj.[[GetOwnProperty]](key).
504
505 let desc =
506 obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
507
508 // 4. Return FromPropertyDescriptor(desc).
509 Self::from_property_descriptor(desc, context)
510 }
511
512 /// `Object.getOwnPropertyDescriptors( object )`
513 ///
514 /// Returns all own property descriptors of a given object.
515 ///
516 /// More information:
517 /// - [ECMAScript reference][spec]
518 /// - [MDN documentation][mdn]
519 ///
520 /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptors
521 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
522 pub fn get_own_property_descriptors(
523 _: &JsValue,
524 args: &[JsValue],
525 context: &mut Context,
526 ) -> JsResult<JsValue> {
527 // 1. Let obj be ? ToObject(O).
528 let obj = args.get_or_undefined(0).to_object(context)?;
529
530 // 2. Let ownKeys be ? obj.[[OwnPropertyKeys]]().
531 let own_keys =
532 obj.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
533
534 // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
535 let descriptors = JsObject::with_object_proto(context.intrinsics());
536
537 // 4. For each element key of ownKeys, do
538 for key in own_keys {
539 // a. Let desc be ? obj.[[GetOwnProperty]](key).
540
541 let desc =
542 obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
543
544 // b. Let descriptor be FromPropertyDescriptor(desc).
545 let descriptor = Self::from_property_descriptor(desc, context)?;
546
547 // c. If descriptor is not undefined,
548 // perform ! CreateDataPropertyOrThrow(descriptors, key, descriptor).
549 if !descriptor.is_undefined() {
550 descriptors
551 .create_data_property_or_throw(key, descriptor, context)
552 .js_expect("should not fail according to spec")?;
553 }
554 }
555
556 // 5. Return descriptors.
557 Ok(descriptors.into())
558 }
559
560 /// The abstract operation `FromPropertyDescriptor`.
561 ///
562 /// [ECMAScript reference][spec]
563 ///
564 /// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor
565 pub(crate) fn from_property_descriptor(
566 desc: Option<PropertyDescriptor>,
567 context: &mut Context,
568 ) -> JsResult<JsValue> {
569 // 1. If Desc is undefined, return undefined.
570 let Some(desc) = desc else {
571 return Ok(JsValue::undefined());
572 };
573
574 // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
575 // 3. Assert: obj is an extensible ordinary object with no own properties.
576 let obj = JsObject::with_object_proto(context.intrinsics());
577
578 // 4. If Desc has a [[Value]] field, then
579 if let Some(value) = desc.value() {
580 // a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]).
581 obj.create_data_property_or_throw(js_string!("value"), value.clone(), context)
582 .js_expect("CreateDataPropertyOrThrow cannot fail here")?;
583 }
584
585 // 5. If Desc has a [[Writable]] field, then
586 if let Some(writable) = desc.writable() {
587 // a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]).
588 obj.create_data_property_or_throw(js_string!("writable"), writable, context)
589 .js_expect("CreateDataPropertyOrThrow cannot fail here")?;
590 }
591
592 // 6. If Desc has a [[Get]] field, then
593 if let Some(get) = desc.get() {
594 // a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]).
595 obj.create_data_property_or_throw(js_string!("get"), get.clone(), context)
596 .js_expect("CreateDataPropertyOrThrow cannot fail here")?;
597 }
598
599 // 7. If Desc has a [[Set]] field, then
600 if let Some(set) = desc.set() {
601 // a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]).
602 obj.create_data_property_or_throw(js_string!("set"), set.clone(), context)
603 .js_expect("CreateDataPropertyOrThrow cannot fail here")?;
604 }
605
606 // 8. If Desc has an [[Enumerable]] field, then
607 if let Some(enumerable) = desc.enumerable() {
608 // a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]).
609 obj.create_data_property_or_throw(js_string!("enumerable"), enumerable, context)
610 .js_expect("CreateDataPropertyOrThrow cannot fail here")?;
611 }
612
613 // 9. If Desc has a [[Configurable]] field, then
614 if let Some(configurable) = desc.configurable() {
615 // a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]).
616 obj.create_data_property_or_throw(js_string!("configurable"), configurable, context)
617 .js_expect("CreateDataPropertyOrThrow cannot fail here")?;
618 }
619
620 // 10. Return obj.
621 Ok(obj.into())
622 }
623
624 /// Uses the `SameValue` algorithm to check equality of objects
625 pub fn is(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
626 let x = args.get_or_undefined(0);
627 let y = args.get_or_undefined(1);
628
629 Ok(JsValue::same_value(x, y).into())
630 }
631
632 /// Get the `prototype` of an object.
633 ///
634 /// [More information][spec]
635 ///
636 /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
637 pub fn get_prototype_of(
638 _: &JsValue,
639 args: &[JsValue],
640 context: &mut Context,
641 ) -> JsResult<JsValue> {
642 if args.is_empty() {
643 return Err(js_error!(TypeError:
644 "Object.getPrototypeOf: At least 1 argument required, but only 0 passed",
645 ));
646 }
647
648 // 1. Let obj be ? ToObject(O).
649 let obj = args[0].clone().to_object(context)?;
650
651 // 2. Return ? obj.[[GetPrototypeOf]]().
652 Ok(obj
653 .__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))?
654 .map_or(JsValue::null(), JsValue::new))
655 }
656
657 /// Set the `prototype` of an object.
658 ///
659 /// [More information][spec]
660 ///
661 /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
662 pub fn set_prototype_of(
663 _: &JsValue,
664 args: &[JsValue],
665 context: &mut Context,
666 ) -> JsResult<JsValue> {
667 if args.len() < 2 {
668 return Err(js_error!(TypeError:
669 "Object.setPrototypeOf: At least 2 arguments required, but only {} passed",
670 args.len()
671 ));
672 }
673
674 // 1. Set O to ? RequireObjectCoercible(O).
675 let o = args
676 .first()
677 .cloned()
678 .unwrap_or_default()
679 .require_object_coercible()?
680 .clone();
681
682 let proto = match args.get_or_undefined(1).variant() {
683 JsVariant::Object(obj) => Some(obj.clone()),
684 JsVariant::Null => None,
685 // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
686 val => {
687 return Err(js_error!(TypeError:
688 "Object.setPrototypeOf: expected 'proto' to be an Object or null, got `{}`",
689 val.type_of()
690 ));
691 }
692 };
693
694 let Some(obj) = o.as_object() else {
695 // 3. If Type(O) is not Object, return O.
696 return Ok(o);
697 };
698
699 // 4. Let status be ? O.[[SetPrototypeOf]](proto).
700 let status =
701 obj.__set_prototype_of__(proto, &mut InternalMethodPropertyContext::new(context))?;
702
703 if !status {
704 return Err(js_error!(TypeError: "can't set prototype of this object"));
705 }
706
707 // 6. Return O.
708 Ok(o)
709 }
710
711 /// `Object.prototype.isPrototypeOf( proto )`
712 ///
713 /// Check whether or not an object exists within another object's prototype chain.
714 ///
715 /// More information:
716 /// - [ECMAScript reference][spec]
717 /// - [MDN documentation][mdn]
718 ///
719 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.isprototypeof
720 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf
721 pub fn is_prototype_of(
722 this: &JsValue,
723 args: &[JsValue],
724 context: &mut Context,
725 ) -> JsResult<JsValue> {
726 let v = args.get_or_undefined(0);
727 if !v.is_object() {
728 return Ok(JsValue::new(false));
729 }
730 let mut v = v.clone();
731 let o = JsValue::new(this.to_object(context)?);
732 loop {
733 v = Self::get_prototype_of(this, &[v], context)?;
734 if v.is_null() {
735 return Ok(JsValue::new(false));
736 }
737 if JsValue::same_value(&o, &v) {
738 return Ok(JsValue::new(true));
739 }
740 }
741 }
742
743 /// Define a property in an object
744 pub fn define_property(
745 _: &JsValue,
746 args: &[JsValue],
747 context: &mut Context,
748 ) -> JsResult<JsValue> {
749 if let Some(object) = args.get_or_undefined(0).as_object() {
750 let key = args
751 .get(1)
752 .unwrap_or(&JsValue::undefined())
753 .to_property_key(context)?;
754 let desc = args
755 .get(2)
756 .unwrap_or(&JsValue::undefined())
757 .to_property_descriptor(context)?;
758
759 object.define_property_or_throw(key, desc, context)?;
760
761 Ok(object.clone().into())
762 } else {
763 Err(js_error!(TypeError: "Object.defineProperty: expected 'this' to be an Object"))
764 }
765 }
766
767 /// `Object.defineProperties( proto, [propertiesObject] )`
768 ///
769 /// Creates or update own properties to the object
770 ///
771 /// More information:
772 /// - [ECMAScript reference][spec]
773 /// - [MDN documentation][mdn]
774 ///
775 /// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
776 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties
777 pub fn define_properties(
778 _: &JsValue,
779 args: &[JsValue],
780 context: &mut Context,
781 ) -> JsResult<JsValue> {
782 let arg = args.get_or_undefined(0);
783 if let Some(obj) = arg.as_object() {
784 let props = args.get_or_undefined(1);
785 object_define_properties(&obj, props, context)?;
786 Ok(arg.clone())
787 } else {
788 Err(js_error!(TypeError: "Object.defineProperties: expected 'this' to be an Object"))
789 }
790 }
791
792 /// `Object.prototype.valueOf()`
793 ///
794 /// More information:
795 /// - [ECMAScript reference][spec]
796 /// - [MDN documentation][mdn]
797 ///
798 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.valueof
799 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf
800 pub fn value_of(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
801 // 1. Return ? ToObject(this value).
802 Ok(this.to_object(context)?.into())
803 }
804
805 /// `Object.prototype.toString()`
806 ///
807 /// This method returns a string representing the object.
808 ///
809 /// More information:
810 /// - [ECMAScript reference][spec]
811 /// - [MDN documentation][mdn]
812 ///
813 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring
814 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
815 #[allow(clippy::wrong_self_convention)]
816 pub fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
817 // 1. If the this value is undefined, return "[object Undefined]".
818 if this.is_undefined() {
819 return Ok(js_string!("[object Undefined]").into());
820 }
821 // 2. If the this value is null, return "[object Null]".
822 if this.is_null() {
823 return Ok(js_string!("[object Null]").into());
824 }
825 // 3. Let O be ! ToObject(this value).
826 let o = this
827 .to_object(context)
828 .js_expect("toObject cannot fail here")?;
829
830 // 4. Let isArray be ? IsArray(O).
831 // 5. If isArray is true, let builtinTag be "Array".
832 let builtin_tag = if o.is_array_abstract()? {
833 js_str!("Array")
834 } else if o.is::<UnmappedArguments>() || o.is::<MappedArguments>() {
835 // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
836 js_str!("Arguments")
837 } else if o.is_callable() {
838 // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function".
839 js_str!("Function")
840 } else if o.is::<Error>() {
841 // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
842 js_str!("Error")
843 } else if o.is::<bool>() {
844 // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
845 js_str!("Boolean")
846 } else if o.is::<f64>() {
847 // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
848 js_str!("Number")
849 } else if o.is::<JsString>() {
850 // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
851 js_str!("String")
852 } else if o.is::<Date>() {
853 // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
854 js_str!("Date")
855 } else if o.is::<RegExp>() {
856 // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
857 js_str!("RegExp")
858 } else {
859 // 14. Else, let builtinTag be "Object".
860 js_str!("Object")
861 };
862
863 // 15. Let tag be ? Get(O, @@toStringTag).
864 let tag = o.get(JsSymbol::to_string_tag(), context)?;
865
866 // 16. If Type(tag) is not String, set tag to builtinTag.
867 let tag = tag.as_string();
868 let tag = tag.as_ref().map_or(builtin_tag, JsString::as_str);
869
870 // 17. Return the string-concatenation of "[object ", tag, and "]".
871 Ok(js_string!(js_str!("[object "), tag, js_str!("]")).into())
872 }
873
874 /// `Object.prototype.toLocaleString( [ reserved1 [ , reserved2 ] ] )`
875 ///
876 /// More information:
877 /// - [ECMAScript reference][spec]
878 /// - [MDN documentation][mdn]
879 ///
880 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tolocalestring
881 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toLocaleString
882 #[allow(clippy::wrong_self_convention)]
883 pub fn to_locale_string(
884 this: &JsValue,
885 _: &[JsValue],
886 context: &mut Context,
887 ) -> JsResult<JsValue> {
888 // 1. Let O be the this value.
889 // 2. Return ? Invoke(O, "toString").
890 this.invoke(js_string!("toString"), &[], context)
891 }
892
893 /// `Object.prototype.hasOwnProperty( property )`
894 ///
895 /// The method returns a boolean indicating whether the object has the specified property
896 /// as its own property (as opposed to inheriting it).
897 ///
898 /// More information:
899 /// - [ECMAScript reference][spec]
900 /// - [MDN documentation][mdn]
901 ///
902 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty
903 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
904 pub fn has_own_property(
905 this: &JsValue,
906 args: &[JsValue],
907 context: &mut Context,
908 ) -> JsResult<JsValue> {
909 // 1. Let P be ? ToPropertyKey(V).
910 let key = args.get_or_undefined(0).to_property_key(context)?;
911
912 // 2. Let O be ? ToObject(this value).
913 let object = this.to_object(context)?;
914
915 // 3. Return ? HasOwnProperty(O, P).
916 Ok(object.has_own_property(key, context)?.into())
917 }
918
919 /// `Object.prototype.propertyIsEnumerable( property )`
920 ///
921 /// This method returns a Boolean indicating whether the specified property is
922 /// enumerable and is the object's own property.
923 ///
924 /// More information:
925 /// - [ECMAScript reference][spec]
926 /// - [MDN documentation][mdn]
927 ///
928 /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
929 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable
930 pub fn property_is_enumerable(
931 this: &JsValue,
932 args: &[JsValue],
933 context: &mut Context,
934 ) -> JsResult<JsValue> {
935 let Some(key) = args.first() else {
936 return Ok(JsValue::new(false));
937 };
938
939 let key = key.to_property_key(context)?;
940
941 let own_prop = this
942 .to_object(context)?
943 .__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
944
945 own_prop
946 .as_ref()
947 .and_then(PropertyDescriptor::enumerable)
948 .unwrap_or_default()
949 .conv::<JsValue>()
950 .pipe(Ok)
951 }
952
953 /// `Object.assign( target, ...sources )`
954 ///
955 /// This method copies all enumerable own properties from one or more
956 /// source objects to a target object. It returns the target object.
957 ///
958 /// More information:
959 /// - [ECMAScript reference][spec]
960 /// - [MDN documentation][mdn]
961 ///
962 /// [spec]: https://tc39.es/ecma262/#sec-object.assign
963 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
964 pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
965 // 1. Let to be ? ToObject(target).
966 let to = args.get_or_undefined(0).to_object(context)?;
967
968 // 2. If only one argument was passed, return to.
969 if args.len() == 1 {
970 return Ok(to.into());
971 }
972
973 // 3. For each element nextSource of sources, do
974 for source in &args[1..] {
975 // 3.a. If nextSource is neither undefined nor null, then
976 if !source.is_null_or_undefined() {
977 // 3.a.i. Let from be ! ToObject(nextSource).
978 let from = source
979 .to_object(context)
980 .js_expect("this ToObject call must not fail")?;
981 // 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]]().
982 let keys =
983 from.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
984 // 3.a.iii. For each element nextKey of keys, do
985 for key in keys {
986 // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
987
988 if let Some(desc) = from.__get_own_property__(
989 &key,
990 &mut InternalMethodPropertyContext::new(context),
991 )? {
992 // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
993 if desc.expect_enumerable() {
994 // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
995 let property = from.get(key.clone(), context)?;
996 // 3.a.iii.2.b. Perform ? Set(to, nextKey, propValue, true).
997 to.set(key, property, true, context)?;
998 }
999 }
1000 }
1001 }
1002 }
1003
1004 // 4. Return to.
1005 Ok(to.into())
1006 }
1007
1008 /// `Object.keys( target )`
1009 ///
1010 /// This method returns an array of a given object's own enumerable
1011 /// property names, iterated in the same order that a normal loop would.
1012 ///
1013 /// More information:
1014 /// - [ECMAScript reference][spec]
1015 /// - [MDN documentation][mdn]
1016 ///
1017 /// [spec]: https://tc39.es/ecma262/#sec-object.keys
1018 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
1019 pub fn keys(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1020 // 1. Let obj be ? ToObject(target).
1021 let obj = args
1022 .first()
1023 .cloned()
1024 .unwrap_or_default()
1025 .to_object(context)?;
1026
1027 // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key).
1028 let name_list = obj.enumerable_own_property_names(PropertyNameKind::Key, context)?;
1029
1030 // 3. Return CreateArrayFromList(nameList).
1031 let result = Array::create_array_from_list(name_list, context);
1032
1033 Ok(result.into())
1034 }
1035
1036 /// `Object.values( target )`
1037 ///
1038 /// More information:
1039 /// - [ECMAScript reference][spec]
1040 /// - [MDN documentation][mdn]
1041 ///
1042 /// [spec]: https://tc39.es/ecma262/#sec-object.values
1043 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
1044 pub fn values(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1045 // 1. Let obj be ? ToObject(target).
1046 let obj = args
1047 .first()
1048 .cloned()
1049 .unwrap_or_default()
1050 .to_object(context)?;
1051
1052 // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, value).
1053 let name_list = obj.enumerable_own_property_names(PropertyNameKind::Value, context)?;
1054
1055 // 3. Return CreateArrayFromList(nameList).
1056 let result = Array::create_array_from_list(name_list, context);
1057
1058 Ok(result.into())
1059 }
1060
1061 /// `Object.entries( target )`
1062 ///
1063 /// This method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
1064 /// This is the same as iterating with a for...in loop,
1065 /// except that a for...in loop enumerates properties in the prototype chain as well).
1066 ///
1067 /// More information:
1068 /// - [ECMAScript reference][spec]
1069 /// - [MDN documentation][mdn]
1070 ///
1071 /// [spec]: https://tc39.es/ecma262/#sec-object.entries
1072 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
1073 pub fn entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1074 // 1. Let obj be ? ToObject(target).
1075 let obj = args
1076 .first()
1077 .cloned()
1078 .unwrap_or_default()
1079 .to_object(context)?;
1080
1081 // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value).
1082 let name_list =
1083 obj.enumerable_own_property_names(PropertyNameKind::KeyAndValue, context)?;
1084
1085 // 3. Return CreateArrayFromList(nameList).
1086 let result = Array::create_array_from_list(name_list, context);
1087
1088 Ok(result.into())
1089 }
1090
1091 /// `Object.seal( target )`
1092 ///
1093 /// More information:
1094 /// - [ECMAScript reference][spec]
1095 /// - [MDN documentation][mdn]
1096 ///
1097 /// [spec]: https://tc39.es/ecma262/#sec-object.seal
1098 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal
1099 pub fn seal(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1100 let o = args.get_or_undefined(0);
1101
1102 if let Some(o) = o.as_object() {
1103 // 2. Let status be ? SetIntegrityLevel(O, sealed).
1104 let status = o.set_integrity_level(IntegrityLevel::Sealed, context)?;
1105 // 3. If status is false, throw a TypeError exception.
1106 if !status {
1107 return Err(js_error!(TypeError: "cannot seal object"));
1108 }
1109 }
1110 // 1. If Type(O) is not Object, return O.
1111 // 4. Return O.
1112 Ok(o.clone())
1113 }
1114
1115 /// `Object.isSealed( target )`
1116 ///
1117 /// More information:
1118 /// - [ECMAScript reference][spec]
1119 /// - [MDN documentation][mdn]
1120 ///
1121 /// [spec]: https://tc39.es/ecma262/#sec-object.issealed
1122 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
1123 pub fn is_sealed(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1124 let o = args.get_or_undefined(0);
1125
1126 // 1. If Type(O) is not Object, return true.
1127 // 2. Return ? TestIntegrityLevel(O, sealed).
1128 if let Some(o) = o.as_object() {
1129 Ok(o.test_integrity_level(IntegrityLevel::Sealed, context)?
1130 .into())
1131 } else {
1132 Ok(JsValue::new(true))
1133 }
1134 }
1135
1136 /// `Object.freeze( target )`
1137 ///
1138 /// More information:
1139 /// - [ECMAScript reference][spec]
1140 /// - [MDN documentation][mdn]
1141 ///
1142 /// [spec]: https://tc39.es/ecma262/#sec-object.freeze
1143 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
1144 pub fn freeze(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1145 let o = args.get_or_undefined(0);
1146
1147 if let Some(o) = o.as_object() {
1148 // 2. Let status be ? SetIntegrityLevel(O, frozen).
1149 let status = o.set_integrity_level(IntegrityLevel::Frozen, context)?;
1150 // 3. If status is false, throw a TypeError exception.
1151 if !status {
1152 return Err(js_error!(TypeError: "cannot freeze object"));
1153 }
1154 }
1155 // 1. If Type(O) is not Object, return O.
1156 // 4. Return O.
1157 Ok(o.clone())
1158 }
1159
1160 /// `Object.isFrozen( target )`
1161 ///
1162 /// More information:
1163 /// - [ECMAScript reference][spec]
1164 /// - [MDN documentation][mdn]
1165 ///
1166 /// [spec]: https://tc39.es/ecma262/#sec-object.isfrozen
1167 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
1168 pub fn is_frozen(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1169 let o = args.get_or_undefined(0);
1170
1171 // 1. If Type(O) is not Object, return true.
1172 // 2. Return ? TestIntegrityLevel(O, frozen).
1173 if let Some(o) = o.as_object() {
1174 Ok(o.test_integrity_level(IntegrityLevel::Frozen, context)?
1175 .into())
1176 } else {
1177 Ok(JsValue::new(true))
1178 }
1179 }
1180
1181 /// `Object.preventExtensions( target )`
1182 ///
1183 /// More information:
1184 /// - [ECMAScript reference][spec]
1185 /// - [MDN documentation][mdn]
1186 ///
1187 /// [spec]: https://tc39.es/ecma262/#sec-object.preventextensions
1188 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
1189 pub fn prevent_extensions(
1190 _: &JsValue,
1191 args: &[JsValue],
1192 context: &mut Context,
1193 ) -> JsResult<JsValue> {
1194 let o = args.get_or_undefined(0);
1195
1196 if let Some(o) = o.as_object() {
1197 // 2. Let status be ? O.[[PreventExtensions]]().
1198 let status =
1199 o.__prevent_extensions__(&mut InternalMethodPropertyContext::new(context))?;
1200 // 3. If status is false, throw a TypeError exception.
1201 if !status {
1202 return Err(js_error!(TypeError: "cannot prevent extensions"));
1203 }
1204 }
1205 // 1. If Type(O) is not Object, return O.
1206 // 4. Return O.
1207 Ok(o.clone())
1208 }
1209
1210 /// `Object.isExtensible( target )`
1211 ///
1212 /// More information:
1213 /// - [ECMAScript reference][spec]
1214 /// - [MDN documentation][mdn]
1215 ///
1216 /// [spec]: https://tc39.es/ecma262/#sec-object.isextensible
1217 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
1218 pub fn is_extensible(
1219 _: &JsValue,
1220 args: &[JsValue],
1221 context: &mut Context,
1222 ) -> JsResult<JsValue> {
1223 let o = args.get_or_undefined(0);
1224 // 1. If Type(O) is not Object, return false.
1225 if let Some(o) = o.as_object() {
1226 // 2. Return ? IsExtensible(O).
1227 Ok(o.is_extensible(context)?.into())
1228 } else {
1229 Ok(JsValue::new(false))
1230 }
1231 }
1232
1233 /// `Object.getOwnPropertyNames( object )`
1234 ///
1235 /// More information:
1236 /// - [ECMAScript reference][spec]
1237 /// - [MDN documentation][mdn]
1238 ///
1239 /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertynames
1240 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
1241 pub fn get_own_property_names(
1242 _: &JsValue,
1243 args: &[JsValue],
1244 context: &mut Context,
1245 ) -> JsResult<JsValue> {
1246 // 1. Return ? GetOwnPropertyKeys(O, string).
1247 let o = args.get_or_undefined(0);
1248 get_own_property_keys(o, PropertyKeyType::String, context)
1249 }
1250
1251 /// `Object.getOwnPropertySymbols( object )`
1252 ///
1253 /// More information:
1254 /// - [ECMAScript reference][spec]
1255 /// - [MDN documentation][mdn]
1256 ///
1257 /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertysymbols
1258 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols
1259 pub fn get_own_property_symbols(
1260 _: &JsValue,
1261 args: &[JsValue],
1262 context: &mut Context,
1263 ) -> JsResult<JsValue> {
1264 // 1. Return ? GetOwnPropertyKeys(O, symbol).
1265 let o = args.get_or_undefined(0);
1266 get_own_property_keys(o, PropertyKeyType::Symbol, context)
1267 }
1268
1269 /// `Object.hasOwn( object, property )`
1270 ///
1271 /// More information:
1272 /// - [ECMAScript reference][spec]
1273 /// - [MDN documentation][mdn]
1274 ///
1275 /// [spec]: https://tc39.es/ecma262/#sec-object.hasown
1276 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
1277 pub fn has_own(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1278 // 1. Let obj be ? ToObject(O).
1279 let obj = args.get_or_undefined(0).to_object(context)?;
1280
1281 // 2. Let key be ? ToPropertyKey(P).
1282 let key = args.get_or_undefined(1).to_property_key(context)?;
1283
1284 // 3. Return ? HasOwnProperty(obj, key).
1285 Ok(obj.has_own_property(key, context)?.into())
1286 }
1287
1288 /// `Object.fromEntries( iterable )`
1289 ///
1290 /// More information:
1291 /// - [ECMAScript reference][spec]
1292 /// - [MDN documentation][mdn]
1293 ///
1294 /// [spec]: https://tc39.es/ecma262/#sec-object.fromentries
1295 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries
1296 pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1297 // 1. Perform ? RequireObjectCoercible(iterable).
1298 let iterable = args.get_or_undefined(0).require_object_coercible()?;
1299
1300 // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
1301 // 3. Assert: obj is an extensible ordinary object with no own properties.
1302 let obj = JsObject::with_object_proto(context.intrinsics());
1303
1304 // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
1305 // obj and performs the following steps when called:
1306 let closure = FunctionObjectBuilder::new(
1307 context.realm(),
1308 NativeFunction::from_copy_closure_with_captures(
1309 |_, args, obj, context| {
1310 let key = args.get_or_undefined(0);
1311 let value = args.get_or_undefined(1);
1312
1313 // a. Let propertyKey be ? ToPropertyKey(key).
1314 let property_key = key.to_property_key(context)?;
1315
1316 // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value).
1317 obj.create_data_property_or_throw(property_key, value.clone(), context)?;
1318
1319 // c. Return undefined.
1320 Ok(JsValue::undefined())
1321 },
1322 obj.clone(),
1323 ),
1324 );
1325
1326 // 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »).
1327 let adder = closure.length(2).name("").build();
1328
1329 // 6. Return ? AddEntriesFromIterable(obj, iterable, adder).
1330 map::add_entries_from_iterable(&obj, iterable, &adder, context)
1331 }
1332
1333 /// [`Object.groupBy ( items, callbackfn )`][spec]
1334 ///
1335 /// [spec]: https://tc39.es/ecma262/#sec-object.groupby
1336 pub(crate) fn group_by(
1337 _: &JsValue,
1338 args: &[JsValue],
1339 context: &mut Context,
1340 ) -> JsResult<JsValue> {
1341 use std::hash::BuildHasherDefault;
1342
1343 use indexmap::IndexMap;
1344 use rustc_hash::FxHasher;
1345
1346 use crate::builtins::{Number, iterable::if_abrupt_close_iterator};
1347
1348 let items = args.get_or_undefined(0);
1349 let callback = args.get_or_undefined(1);
1350 // 1. Let groups be ? GroupBy(items, callbackfn, property).
1351
1352 // `GroupBy`
1353 // https://tc39.es/ecma262/#sec-groupby
1354 // inlined to change the key type.
1355
1356 // 1. Perform ? RequireObjectCoercible(items).
1357 items.require_object_coercible()?;
1358
1359 // 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
1360 let callback = callback
1361 .as_callable()
1362 .ok_or_else(|| js_error!(TypeError: "callback must be a callable object"))?;
1363
1364 // 3. Let groups be a new empty List.
1365 let mut groups: IndexMap<PropertyKey, Vec<JsValue>, BuildHasherDefault<FxHasher>> =
1366 IndexMap::default();
1367
1368 // 4. Let iteratorRecord be ? GetIterator(items, sync).
1369 let mut iterator = items.get_iterator(IteratorHint::Sync, context)?;
1370
1371 // 5. Let k be 0.
1372 let mut k = 0u64;
1373
1374 // 6. Repeat,
1375 loop {
1376 // a. If k ≥ 2^53 - 1, then
1377 if k >= Number::MAX_SAFE_INTEGER as u64 {
1378 // i. Let error be ThrowCompletion(a newly created TypeError object).
1379 let error = js_error!(TypeError: "exceeded maximum safe integer");
1380
1381 // ii. Return ? IteratorClose(iteratorRecord, error).
1382 return iterator.close(Err(error), context);
1383 }
1384
1385 // b. Let next be ? IteratorStepValue(iteratorRecord).
1386 let Some(next) = iterator.step_value(context)? else {
1387 // c. If next is false, then
1388 // i. Return groups.
1389 break;
1390 };
1391
1392 // d. Let value be next.
1393 let value = next;
1394
1395 // e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
1396 let key = callback.call(&JsValue::undefined(), &[value.clone(), k.into()], context);
1397
1398 // f. IfAbruptCloseIterator(key, iteratorRecord).
1399 let key = if_abrupt_close_iterator!(key, iterator, context);
1400
1401 // g. If keyCoercion is property, then
1402 // i. Set key to Completion(ToPropertyKey(key)).
1403 let key = key.to_property_key(context);
1404
1405 // ii. IfAbruptCloseIterator(key, iteratorRecord).
1406 let key = if_abrupt_close_iterator!(key, iterator, context);
1407
1408 // i. Perform AddValueToKeyedGroup(groups, key, value).
1409 groups.entry(key).or_default().push(value);
1410
1411 // j. Set k to k + 1.
1412 k += 1;
1413 }
1414
1415 // 2. Let obj be OrdinaryObjectCreate(null).
1416 let obj = JsObject::with_null_proto();
1417
1418 // 3. For each Record { [[Key]], [[Elements]] } g of groups, do
1419 for (key, elements) in groups {
1420 // a. Let elements be CreateArrayFromList(g.[[Elements]]).
1421 let elements = Array::create_array_from_list(elements, context);
1422
1423 // b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
1424 obj.create_data_property_or_throw(key, elements, context)
1425 .js_expect("cannot fail for a newly created object")?;
1426 }
1427
1428 // 4. Return obj.
1429 Ok(obj.into())
1430 }
1431}
1432
1433/// The abstract operation `ObjectDefineProperties`
1434///
1435/// More information:
1436/// - [ECMAScript reference][spec]
1437///
1438/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
1439fn object_define_properties(
1440 object: &JsObject,
1441 props: &JsValue,
1442 context: &mut Context,
1443) -> JsResult<()> {
1444 // 1. Assert: Type(O) is Object.
1445 // 2. Let props be ? ToObject(Properties).
1446 let props = &props.to_object(context)?;
1447
1448 // 3. Let keys be ? props.[[OwnPropertyKeys]]().
1449 let keys = props.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
1450
1451 // 4. Let descriptors be a new empty List.
1452 let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
1453
1454 // 5. For each element nextKey of keys, do
1455 for next_key in keys {
1456 // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
1457 // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
1458
1459 if let Some(prop_desc) = props
1460 .__get_own_property__(&next_key, &mut InternalMethodPropertyContext::new(context))?
1461 && prop_desc.expect_enumerable()
1462 {
1463 // i. Let descObj be ? Get(props, nextKey).
1464 let desc_obj = props.get(next_key.clone(), context)?;
1465
1466 // ii. Let desc be ? ToPropertyDescriptor(descObj).
1467 let desc = desc_obj.to_property_descriptor(context)?;
1468
1469 // iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
1470 descriptors.push((next_key, desc));
1471 }
1472 }
1473
1474 // 6. For each element pair of descriptors, do
1475 // a. Let P be the first element of pair.
1476 // b. Let desc be the second element of pair.
1477 for (p, d) in descriptors {
1478 // c. Perform ? DefinePropertyOrThrow(O, P, desc).
1479 object.define_property_or_throw(p, d, context)?;
1480 }
1481
1482 // 7. Return O.
1483 Ok(())
1484}
1485
1486/// Type enum used in the abstract operation `GetOwnPropertyKeys`.
1487#[derive(Debug, Copy, Clone)]
1488enum PropertyKeyType {
1489 String,
1490 Symbol,
1491}
1492
1493/// The abstract operation `GetOwnPropertyKeys`.
1494///
1495/// More information:
1496/// - [ECMAScript reference][spec]
1497///
1498/// [spec]: https://tc39.es/ecma262/#sec-getownpropertykeys
1499fn get_own_property_keys(
1500 o: &JsValue,
1501 r#type: PropertyKeyType,
1502 context: &mut Context,
1503) -> JsResult<JsValue> {
1504 // 1. Let obj be ? ToObject(o).
1505 let obj = o.to_object(context)?;
1506
1507 // 2. Let keys be ? obj.[[OwnPropertyKeys]]().
1508 let keys = obj.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
1509
1510 // 3. Let nameList be a new empty List.
1511 // 4. For each element nextKey of keys, do
1512 let name_list = keys.iter().filter_map(|next_key| {
1513 // a. If Type(nextKey) is Symbol and type is symbol or Type(nextKey) is String and type is string, then
1514 // i. Append nextKey as the last element of nameList.
1515 match (r#type, &next_key) {
1516 (PropertyKeyType::String, PropertyKey::String(_))
1517 | (PropertyKeyType::Symbol, PropertyKey::Symbol(_)) => Some(next_key.into()),
1518 (PropertyKeyType::String, PropertyKey::Index(index)) => {
1519 Some(js_string!(index.get()).into())
1520 }
1521 _ => None,
1522 }
1523 });
1524
1525 // 5. Return CreateArrayFromList(nameList).
1526 Ok(Array::create_array_from_list(name_list, context).into())
1527}