boa_engine/object/
mod.rs

1//! Boa's representation of a JavaScript object and builtin object wrappers
2//!
3//! For the builtin object wrappers, please see [`object::builtins`][builtins] for implementors.
4
5pub use jsobject::{RecursionLimiter, Ref, RefMut};
6pub use operations::IntegrityLevel;
7pub use property_map::*;
8use thin_vec::ThinVec;
9
10use self::{internal_methods::ORDINARY_INTERNAL_METHODS, shape::Shape};
11use crate::{
12    builtins::{
13        function::{
14            arguments::{MappedArguments, UnmappedArguments},
15            ConstructorKind,
16        },
17        typed_array::{TypedArray, TypedArrayKind},
18        OrdinaryObject,
19    },
20    context::intrinsics::StandardConstructor,
21    js_string,
22    native_function::{NativeFunction, NativeFunctionObject},
23    property::{Attribute, PropertyDescriptor, PropertyKey},
24    realm::Realm,
25    string::StaticJsStrings,
26    Context, JsString, JsSymbol, JsValue,
27};
28
29use boa_gc::{Finalize, Trace};
30use std::{
31    any::{Any, TypeId},
32    fmt::Debug,
33};
34
35#[cfg(test)]
36mod tests;
37
38pub(crate) mod internal_methods;
39
40pub mod builtins;
41mod datatypes;
42mod jsobject;
43mod operations;
44mod property_map;
45pub mod shape;
46
47pub(crate) use builtins::*;
48
49pub use datatypes::JsData;
50pub use jsobject::*;
51
52/// Const `constructor`, usually set on prototypes as a key to point to their respective constructor object.
53pub const CONSTRUCTOR: JsString = js_string!("constructor");
54
55/// Const `prototype`, usually set on constructors as a key to point to their respective prototype object.
56pub const PROTOTYPE: JsString = js_string!("prototype");
57
58/// A type alias for an object prototype.
59///
60/// A `None` values means that the prototype is the `null` value.
61pub type JsPrototype = Option<JsObject>;
62
63/// The internal storage of an object's property values.
64///
65/// The [`shape::Shape`] contains the property names and attributes.
66pub(crate) type ObjectStorage = Vec<JsValue>;
67
68/// This trait allows Rust types to be passed around as objects.
69///
70/// This is automatically implemented when a type implements `Any`, `Trace`, and `JsData`.
71pub trait NativeObject: Any + Trace + JsData {
72    /// Convert the Rust type which implements `NativeObject` to a `&dyn Any`.
73    fn as_any(&self) -> &dyn Any;
74
75    /// Convert the Rust type which implements `NativeObject` to a `&mut dyn Any`.
76    fn as_mut_any(&mut self) -> &mut dyn Any;
77
78    /// Gets the type name of the value.
79    fn type_name_of_value(&self) -> &'static str;
80}
81
82// TODO: Use super trait casting in Rust 1.75
83impl<T: Any + Trace + JsData> NativeObject for T {
84    fn as_any(&self) -> &dyn Any {
85        self
86    }
87
88    fn as_mut_any(&mut self) -> &mut dyn Any {
89        self
90    }
91
92    fn type_name_of_value(&self) -> &'static str {
93        fn name_of_val<T: ?Sized>(_val: &T) -> &'static str {
94            std::any::type_name::<T>()
95        }
96
97        name_of_val(self)
98    }
99}
100
101// TODO: Use super trait casting in Rust 1.75
102impl dyn NativeObject {
103    /// Returns `true` if the inner type is the same as `T`.
104    #[inline]
105    pub fn is<T: NativeObject>(&self) -> bool {
106        // Get `TypeId` of the type this function is instantiated with.
107        let t = TypeId::of::<T>();
108
109        // Get `TypeId` of the type in the trait object (`self`).
110        let concrete = self.type_id();
111
112        // Compare both `TypeId`s on equality.
113        t == concrete
114    }
115
116    /// Returns some reference to the inner value if it is of type `T`, or
117    /// `None` if it isn't.
118    #[inline]
119    pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> {
120        if self.is::<T>() {
121            // SAFETY: just checked whether we are pointing to the correct type, and we can rely on
122            // that check for memory safety because we have implemented NativeObject for all types; no other
123            // impls can exist as they would conflict with our impl.
124            unsafe { Some(self.downcast_ref_unchecked()) }
125        } else {
126            None
127        }
128    }
129
130    /// Returns some mutable reference to the inner value if it is of type `T`, or
131    /// `None` if it isn't.
132    #[inline]
133    pub fn downcast_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
134        if self.is::<T>() {
135            // SAFETY: Already checked if inner type is T, so this is safe.
136            unsafe { Some(self.downcast_mut_unchecked()) }
137        } else {
138            None
139        }
140    }
141
142    /// Returns a reference to the inner value as type `dyn T`.
143    ///
144    /// # Safety
145    ///
146    /// The contained value must be of type `T`. Calling this method
147    /// with the incorrect type is *undefined behavior*.
148    #[inline]
149    pub unsafe fn downcast_ref_unchecked<T: NativeObject>(&self) -> &T {
150        debug_assert!(self.is::<T>());
151        let ptr: *const dyn NativeObject = self;
152        // SAFETY: caller guarantees that T is the correct type
153        unsafe { &*ptr.cast::<T>() }
154    }
155
156    /// Returns a mutable reference to the inner value as type `dyn T`.
157    ///
158    /// # Safety
159    ///
160    /// The contained value must be of type `T`. Calling this method
161    /// with the incorrect type is *undefined behavior*.
162    #[inline]
163    pub unsafe fn downcast_mut_unchecked<T: NativeObject>(&mut self) -> &mut T {
164        debug_assert!(self.is::<T>());
165        // SAFETY: caller guarantees that T is the correct type
166        let ptr: *mut dyn NativeObject = self;
167        unsafe { &mut *ptr.cast::<T>() }
168    }
169}
170
171/// The internal representation of a JavaScript object.
172#[derive(Debug, Finalize, Trace)]
173// SAFETY: This does not implement drop, so this is safe.
174#[boa_gc(unsafe_no_drop)]
175pub struct Object<T: ?Sized> {
176    /// The collection of properties contained in the object
177    pub(crate) properties: PropertyMap,
178    /// Whether it can have new properties added to it.
179    pub(crate) extensible: bool,
180    /// The `[[PrivateElements]]` internal slot.
181    private_elements: ThinVec<(PrivateName, PrivateElement)>,
182    /// The inner object data
183    pub(crate) data: T,
184}
185
186impl<T: Default> Default for Object<T> {
187    fn default() -> Self {
188        Self {
189            properties: PropertyMap::default(),
190            extensible: true,
191            private_elements: ThinVec::new(),
192            data: T::default(),
193        }
194    }
195}
196
197/// A Private Name.
198#[derive(Clone, Debug, PartialEq, Eq, Trace, Finalize)]
199pub struct PrivateName {
200    /// The `[[Description]]` internal slot of the private name.
201    description: JsString,
202
203    /// The unique identifier of the private name.
204    id: usize,
205}
206
207impl PrivateName {
208    /// Create a new private name.
209    pub(crate) const fn new(description: JsString, id: usize) -> Self {
210        Self { description, id }
211    }
212}
213
214/// The representation of private object elements.
215#[derive(Clone, Debug, Trace, Finalize)]
216pub enum PrivateElement {
217    /// A private field.
218    Field(JsValue),
219
220    /// A private method.
221    Method(JsObject),
222
223    /// A private element accessor.
224    Accessor {
225        /// A getter function.
226        getter: Option<JsObject>,
227
228        /// A setter function.
229        setter: Option<JsObject>,
230    },
231}
232
233impl<T: ?Sized> Object<T> {
234    /// Returns the shape of the object.
235    #[must_use]
236    pub const fn shape(&self) -> &Shape {
237        &self.properties.shape
238    }
239
240    /// Returns the data of the object.
241    #[inline]
242    #[must_use]
243    pub const fn data(&self) -> &T {
244        &self.data
245    }
246
247    /// Returns the data of the object.
248    #[inline]
249    #[must_use]
250    pub fn data_mut(&mut self) -> &mut T {
251        &mut self.data
252    }
253
254    /// Gets the prototype instance of this object.
255    #[inline]
256    #[must_use]
257    pub fn prototype(&self) -> JsPrototype {
258        self.properties.shape.prototype()
259    }
260
261    /// Sets the prototype instance of the object.
262    ///
263    /// [More information][spec]
264    ///
265    /// [spec]: https://tc39.es/ecma262/#sec-invariants-of-the-essential-internal-methods
266    #[track_caller]
267    pub fn set_prototype<O: Into<JsPrototype>>(&mut self, prototype: O) -> bool {
268        let prototype = prototype.into();
269        if self.extensible {
270            self.properties.shape = self.properties.shape.change_prototype_transition(prototype);
271            true
272        } else {
273            // If target is non-extensible, [[SetPrototypeOf]] must return false
274            // unless V is the SameValue as the target's observed [[GetPrototypeOf]] value.
275            self.prototype() == prototype
276        }
277    }
278
279    /// Returns the properties of the object.
280    #[inline]
281    #[must_use]
282    pub const fn properties(&self) -> &PropertyMap {
283        &self.properties
284    }
285
286    #[inline]
287    pub(crate) fn properties_mut(&mut self) -> &mut PropertyMap {
288        &mut self.properties
289    }
290
291    /// Inserts a field in the object `properties` without checking if it's writable.
292    ///
293    /// If a field was already in the object with the same name, then `true` is returned
294    /// otherwise, `false` is returned.
295    pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> bool
296    where
297        K: Into<PropertyKey>,
298        P: Into<PropertyDescriptor>,
299    {
300        self.properties.insert(&key.into(), property.into())
301    }
302
303    /// Helper function for property removal without checking if it's configurable.
304    ///
305    /// Returns `true` if the property was removed, `false` otherwise.
306    #[inline]
307    pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool {
308        self.properties.remove(key)
309    }
310
311    /// Append a private element to an object.
312    pub(crate) fn append_private_element(&mut self, name: PrivateName, element: PrivateElement) {
313        if let PrivateElement::Accessor { getter, setter } = &element {
314            for (key, value) in &mut self.private_elements {
315                if name == *key {
316                    if let PrivateElement::Accessor {
317                        getter: existing_getter,
318                        setter: existing_setter,
319                    } = value
320                    {
321                        if existing_getter.is_none() {
322                            existing_getter.clone_from(getter);
323                        }
324                        if existing_setter.is_none() {
325                            existing_setter.clone_from(setter);
326                        }
327                        return;
328                    }
329                }
330            }
331        }
332
333        self.private_elements.push((name, element));
334    }
335}
336
337impl Object<dyn NativeObject> {
338    /// Return `true` if it is a native object and the native type is `T`.
339    #[must_use]
340    pub fn is<T: NativeObject>(&self) -> bool {
341        self.data.is::<T>()
342    }
343
344    /// Downcast a reference to the object,
345    /// if the object is type native object type `T`.
346    #[must_use]
347    pub fn downcast_ref<T: NativeObject>(&self) -> Option<&T> {
348        self.data.downcast_ref::<T>()
349    }
350
351    /// Downcast a mutable reference to the object,
352    /// if the object is type native object type `T`.
353    pub fn downcast_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
354        self.data.downcast_mut::<T>()
355    }
356
357    /// Checks if this object is an `Arguments` object.
358    pub(crate) fn is_arguments(&self) -> bool {
359        self.is::<UnmappedArguments>() || self.is::<MappedArguments>()
360    }
361
362    /// Checks if it a `Uint8Array` object.
363    #[inline]
364    #[must_use]
365    pub fn is_typed_uint8_array(&self) -> bool {
366        if let Some(int) = self.downcast_ref::<TypedArray>() {
367            matches!(int.kind(), TypedArrayKind::Uint8)
368        } else {
369            false
370        }
371    }
372
373    /// Checks if it a `Int8Array` object.
374    #[inline]
375    #[must_use]
376    pub fn is_typed_int8_array(&self) -> bool {
377        if let Some(int) = self.downcast_ref::<TypedArray>() {
378            matches!(int.kind(), TypedArrayKind::Int8)
379        } else {
380            false
381        }
382    }
383
384    /// Checks if it a `Uint16Array` object.
385    #[inline]
386    #[must_use]
387    pub fn is_typed_uint16_array(&self) -> bool {
388        if let Some(int) = self.downcast_ref::<TypedArray>() {
389            matches!(int.kind(), TypedArrayKind::Uint16)
390        } else {
391            false
392        }
393    }
394
395    /// Checks if it a `Int16Array` object.
396    #[inline]
397    #[must_use]
398    pub fn is_typed_int16_array(&self) -> bool {
399        if let Some(int) = self.downcast_ref::<TypedArray>() {
400            matches!(int.kind(), TypedArrayKind::Int16)
401        } else {
402            false
403        }
404    }
405
406    /// Checks if it a `Uint32Array` object.
407    #[inline]
408    #[must_use]
409    pub fn is_typed_uint32_array(&self) -> bool {
410        if let Some(int) = self.downcast_ref::<TypedArray>() {
411            matches!(int.kind(), TypedArrayKind::Uint32)
412        } else {
413            false
414        }
415    }
416
417    /// Checks if it a `Int32Array` object.
418    #[inline]
419    #[must_use]
420    pub fn is_typed_int32_array(&self) -> bool {
421        if let Some(int) = self.downcast_ref::<TypedArray>() {
422            matches!(int.kind(), TypedArrayKind::Int32)
423        } else {
424            false
425        }
426    }
427
428    /// Checks if it a `Float32Array` object.
429    #[inline]
430    #[must_use]
431    pub fn is_typed_float32_array(&self) -> bool {
432        if let Some(int) = self.downcast_ref::<TypedArray>() {
433            matches!(int.kind(), TypedArrayKind::Float32)
434        } else {
435            false
436        }
437    }
438
439    /// Checks if it a `Float64Array` object.
440    #[inline]
441    #[must_use]
442    pub fn is_typed_float64_array(&self) -> bool {
443        if let Some(int) = self.downcast_ref::<TypedArray>() {
444            matches!(int.kind(), TypedArrayKind::Float64)
445        } else {
446            false
447        }
448    }
449}
450
451/// The functions binding.
452///
453/// Specifies what is the name of the function object (`name` property),
454/// and the binding name of the function object which can be different
455/// from the function name.
456///
457/// The only way to construct this is with the `From` trait.
458///
459/// There are two implementations:
460///  - From a single type `T` which implements `Into<FunctionBinding>` which sets the binding
461///    name and the function name to the same value.
462///  - From a tuple `(B: Into<PropertyKey>, N: Into<JsString>)`, where the `B` is the binding name
463///    and the `N` is the function name.
464#[derive(Debug, Clone)]
465pub struct FunctionBinding {
466    pub(crate) binding: PropertyKey,
467    pub(crate) name: JsString,
468}
469
470impl From<JsString> for FunctionBinding {
471    #[inline]
472    fn from(name: JsString) -> Self {
473        Self {
474            binding: name.clone().into(),
475            name,
476        }
477    }
478}
479
480impl From<JsSymbol> for FunctionBinding {
481    #[inline]
482    fn from(binding: JsSymbol) -> Self {
483        Self {
484            name: binding.fn_name(),
485            binding: binding.into(),
486        }
487    }
488}
489
490impl<B, N> From<(B, N)> for FunctionBinding
491where
492    B: Into<PropertyKey>,
493    N: Into<JsString>,
494{
495    fn from((binding, name): (B, N)) -> Self {
496        Self {
497            binding: binding.into(),
498            name: name.into(),
499        }
500    }
501}
502
503/// Builder for creating native function objects
504#[derive(Debug)]
505pub struct FunctionObjectBuilder<'realm> {
506    realm: &'realm Realm,
507    function: NativeFunction,
508    constructor: Option<ConstructorKind>,
509    name: JsString,
510    length: usize,
511}
512
513impl<'realm> FunctionObjectBuilder<'realm> {
514    /// Create a new `FunctionBuilder` for creating a native function.
515    #[inline]
516    #[must_use]
517    pub fn new(realm: &'realm Realm, function: NativeFunction) -> Self {
518        Self {
519            realm,
520            function,
521            constructor: None,
522            name: js_string!(),
523            length: 0,
524        }
525    }
526
527    /// Specify the name property of object function object.
528    ///
529    /// The default is `""` (empty string).
530    #[must_use]
531    pub fn name<N>(mut self, name: N) -> Self
532    where
533        N: Into<JsString>,
534    {
535        self.name = name.into();
536        self
537    }
538
539    /// Specify the length property of object function object.
540    ///
541    /// How many arguments this function takes.
542    ///
543    /// The default is `0`.
544    #[inline]
545    #[must_use]
546    pub const fn length(mut self, length: usize) -> Self {
547        self.length = length;
548        self
549    }
550
551    /// Specify whether the object function object can be called with `new` keyword.
552    ///
553    /// The default is `false`.
554    #[must_use]
555    pub fn constructor(mut self, yes: bool) -> Self {
556        self.constructor = yes.then_some(ConstructorKind::Base);
557        self
558    }
559
560    /// Build the function object.
561    #[must_use]
562    pub fn build(self) -> JsFunction {
563        let object = self.realm.intrinsics().templates().function().create(
564            NativeFunctionObject {
565                f: self.function,
566                constructor: self.constructor,
567                realm: Some(self.realm.clone()),
568            },
569            vec![self.length.into(), self.name.into()],
570        );
571
572        JsFunction::from_object_unchecked(object)
573    }
574}
575
576/// Builder for creating objects with properties.
577///
578/// # Examples
579///
580/// ```
581/// # use boa_engine::{
582/// #     Context,
583/// #     JsValue,
584/// #     NativeFunction,
585/// #     object::ObjectInitializer,
586/// #     property::Attribute,
587/// #     js_string,
588/// # };
589/// let mut context = Context::default();
590/// let object = ObjectInitializer::new(&mut context)
591///     .property(js_string!("hello"), js_string!("world"), Attribute::all())
592///     .property(1, 1, Attribute::all())
593///     .function(
594///         NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())),
595///         js_string!("func"),
596///         0,
597///     )
598///     .build();
599/// ```
600///
601/// The equivalent in JavaScript would be:
602/// ```text
603/// let object = {
604///     hello: "world",
605///     "1": 1,
606///     func: function() {}
607/// }
608/// ```
609#[derive(Debug)]
610pub struct ObjectInitializer<'ctx> {
611    context: &'ctx mut Context,
612    object: JsObject,
613}
614
615impl<'ctx> ObjectInitializer<'ctx> {
616    /// Create a new `ObjectBuilder`.
617    #[inline]
618    pub fn new(context: &'ctx mut Context) -> Self {
619        let object = JsObject::with_object_proto(context.intrinsics());
620        Self { context, object }
621    }
622
623    /// Create a new `ObjectBuilder` with custom [`NativeObject`] data.
624    pub fn with_native_data<T: NativeObject>(data: T, context: &'ctx mut Context) -> Self {
625        let object = JsObject::from_proto_and_data_with_shared_shape(
626            context.root_shape(),
627            context.intrinsics().constructors().object().prototype(),
628            data,
629        );
630        Self { context, object }
631    }
632
633    /// Create a new `ObjectBuilder` with custom [`NativeObject`] data and custom prototype.
634    pub fn with_native_data_and_proto<T: NativeObject>(
635        data: T,
636        proto: JsObject,
637        context: &'ctx mut Context,
638    ) -> Self {
639        let object =
640            JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data);
641        Self { context, object }
642    }
643
644    /// Add a function to the object.
645    pub fn function<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
646    where
647        B: Into<FunctionBinding>,
648    {
649        let binding = binding.into();
650        let function = FunctionObjectBuilder::new(self.context.realm(), function)
651            .name(binding.name)
652            .length(length)
653            .constructor(false)
654            .build();
655
656        self.object.borrow_mut().insert(
657            binding.binding,
658            PropertyDescriptor::builder()
659                .value(function)
660                .writable(true)
661                .enumerable(false)
662                .configurable(true),
663        );
664        self
665    }
666
667    /// Add a property to the object.
668    pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
669    where
670        K: Into<PropertyKey>,
671        V: Into<JsValue>,
672    {
673        let property = PropertyDescriptor::builder()
674            .value(value)
675            .writable(attribute.writable())
676            .enumerable(attribute.enumerable())
677            .configurable(attribute.configurable());
678        self.object.borrow_mut().insert(key, property);
679        self
680    }
681
682    /// Add new accessor property to the object.
683    ///
684    /// # Panics
685    ///
686    /// If both getter or setter are [`None`].
687    pub fn accessor<K>(
688        &mut self,
689        key: K,
690        get: Option<JsFunction>,
691        set: Option<JsFunction>,
692        attribute: Attribute,
693    ) -> &mut Self
694    where
695        K: Into<PropertyKey>,
696    {
697        // Accessors should have at least one function.
698        assert!(set.is_some() || get.is_some());
699
700        let property = PropertyDescriptor::builder()
701            .maybe_get(get)
702            .maybe_set(set)
703            .enumerable(attribute.enumerable())
704            .configurable(attribute.configurable());
705        self.object.borrow_mut().insert(key, property);
706        self
707    }
708
709    /// Build the object.
710    #[inline]
711    pub fn build(&mut self) -> JsObject {
712        self.object.clone()
713    }
714
715    /// Gets the context used to create the object.
716    #[inline]
717    pub fn context(&mut self) -> &mut Context {
718        self.context
719    }
720}
721
722/// Builder for creating constructors objects, like `Array`.
723#[derive(Debug)]
724pub struct ConstructorBuilder<'ctx> {
725    context: &'ctx mut Context,
726    function: NativeFunction,
727    constructor_object: Object<OrdinaryObject>,
728    has_prototype_property: bool,
729    prototype: Object<OrdinaryObject>,
730    name: JsString,
731    length: usize,
732    callable: bool,
733    kind: Option<ConstructorKind>,
734    inherit: Option<JsPrototype>,
735    custom_prototype: Option<JsPrototype>,
736}
737
738impl<'ctx> ConstructorBuilder<'ctx> {
739    /// Create a new `ConstructorBuilder`.
740    #[inline]
741    pub fn new(context: &'ctx mut Context, function: NativeFunction) -> ConstructorBuilder<'ctx> {
742        Self {
743            context,
744            function,
745            constructor_object: Object {
746                data: OrdinaryObject,
747                properties: PropertyMap::default(),
748                extensible: true,
749                private_elements: ThinVec::new(),
750            },
751            prototype: Object {
752                data: OrdinaryObject,
753                properties: PropertyMap::default(),
754                extensible: true,
755                private_elements: ThinVec::new(),
756            },
757            length: 0,
758            name: js_string!(),
759            callable: true,
760            kind: Some(ConstructorKind::Base),
761            inherit: None,
762            custom_prototype: None,
763            has_prototype_property: true,
764        }
765    }
766
767    /// Add new method to the constructors prototype.
768    pub fn method<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
769    where
770        B: Into<FunctionBinding>,
771    {
772        let binding = binding.into();
773        let function = FunctionObjectBuilder::new(self.context.realm(), function)
774            .name(binding.name)
775            .length(length)
776            .constructor(false)
777            .build();
778
779        self.prototype.insert(
780            binding.binding,
781            PropertyDescriptor::builder()
782                .value(function)
783                .writable(true)
784                .enumerable(false)
785                .configurable(true),
786        );
787        self
788    }
789
790    /// Add new static method to the constructors object itself.
791    pub fn static_method<B>(
792        &mut self,
793        function: NativeFunction,
794        binding: B,
795        length: usize,
796    ) -> &mut Self
797    where
798        B: Into<FunctionBinding>,
799    {
800        let binding = binding.into();
801        let function = FunctionObjectBuilder::new(self.context.realm(), function)
802            .name(binding.name)
803            .length(length)
804            .constructor(false)
805            .build();
806
807        self.constructor_object.insert(
808            binding.binding,
809            PropertyDescriptor::builder()
810                .value(function)
811                .writable(true)
812                .enumerable(false)
813                .configurable(true),
814        );
815        self
816    }
817
818    /// Add new data property to the constructor's prototype.
819    pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
820    where
821        K: Into<PropertyKey>,
822        V: Into<JsValue>,
823    {
824        let property = PropertyDescriptor::builder()
825            .value(value)
826            .writable(attribute.writable())
827            .enumerable(attribute.enumerable())
828            .configurable(attribute.configurable());
829        self.prototype.insert(key, property);
830        self
831    }
832
833    /// Add new static data property to the constructor object itself.
834    pub fn static_property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
835    where
836        K: Into<PropertyKey>,
837        V: Into<JsValue>,
838    {
839        let property = PropertyDescriptor::builder()
840            .value(value)
841            .writable(attribute.writable())
842            .enumerable(attribute.enumerable())
843            .configurable(attribute.configurable());
844        self.constructor_object.insert(key, property);
845        self
846    }
847
848    /// Add new accessor property to the constructor's prototype.
849    pub fn accessor<K>(
850        &mut self,
851        key: K,
852        get: Option<JsFunction>,
853        set: Option<JsFunction>,
854        attribute: Attribute,
855    ) -> &mut Self
856    where
857        K: Into<PropertyKey>,
858    {
859        let property = PropertyDescriptor::builder()
860            .maybe_get(get)
861            .maybe_set(set)
862            .enumerable(attribute.enumerable())
863            .configurable(attribute.configurable());
864        self.prototype.insert(key, property);
865        self
866    }
867
868    /// Add new static accessor property to the constructor object itself.
869    pub fn static_accessor<K>(
870        &mut self,
871        key: K,
872        get: Option<JsFunction>,
873        set: Option<JsFunction>,
874        attribute: Attribute,
875    ) -> &mut Self
876    where
877        K: Into<PropertyKey>,
878    {
879        let property = PropertyDescriptor::builder()
880            .maybe_get(get)
881            .maybe_set(set)
882            .enumerable(attribute.enumerable())
883            .configurable(attribute.configurable());
884        self.constructor_object.insert(key, property);
885        self
886    }
887
888    /// Add new property to the constructor's prototype.
889    pub fn property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self
890    where
891        K: Into<PropertyKey>,
892        P: Into<PropertyDescriptor>,
893    {
894        let property = property.into();
895        self.prototype.insert(key, property);
896        self
897    }
898
899    /// Add new static property to the constructor object itself.
900    pub fn static_property_descriptor<K, P>(&mut self, key: K, property: P) -> &mut Self
901    where
902        K: Into<PropertyKey>,
903        P: Into<PropertyDescriptor>,
904    {
905        let property = property.into();
906        self.constructor_object.insert(key, property);
907        self
908    }
909
910    /// Specify how many arguments the constructor function takes.
911    ///
912    /// Default is `0`.
913    #[inline]
914    pub fn length(&mut self, length: usize) -> &mut Self {
915        self.length = length;
916        self
917    }
918
919    /// Specify the name of the constructor function.
920    ///
921    /// Default is `"[object]"`
922    pub fn name<N>(&mut self, name: N) -> &mut Self
923    where
924        N: AsRef<str>,
925    {
926        self.name = name.as_ref().into();
927        self
928    }
929
930    /// Specify whether the constructor function can be called.
931    ///
932    /// Default is `true`
933    #[inline]
934    pub fn callable(&mut self, callable: bool) -> &mut Self {
935        self.callable = callable;
936        self
937    }
938
939    /// Specify whether the constructor function can be called with `new` keyword.
940    ///
941    /// Default is `true`
942    #[inline]
943    pub fn constructor(&mut self, constructor: bool) -> &mut Self {
944        self.kind = constructor.then_some(ConstructorKind::Base);
945        self
946    }
947
948    /// Specify the parent prototype which objects created by this constructor
949    /// inherit from.
950    ///
951    /// Default is `Object.prototype`
952    pub fn inherit<O: Into<JsPrototype>>(&mut self, prototype: O) -> &mut Self {
953        self.inherit = Some(prototype.into());
954        self
955    }
956
957    /// Specify the `[[Prototype]]` internal field of this constructor.
958    ///
959    /// Default is `Function.prototype`
960    pub fn custom_prototype<O: Into<JsPrototype>>(&mut self, prototype: O) -> &mut Self {
961        self.custom_prototype = Some(prototype.into());
962        self
963    }
964
965    /// Specify whether the constructor function has a 'prototype' property.
966    ///
967    /// Default is `true`
968    #[inline]
969    pub fn has_prototype_property(&mut self, has_prototype_property: bool) -> &mut Self {
970        self.has_prototype_property = has_prototype_property;
971        self
972    }
973
974    /// Return the current context.
975    #[inline]
976    pub fn context(&mut self) -> &mut Context {
977        self.context
978    }
979
980    /// Build the constructor function object.
981    #[must_use]
982    pub fn build(mut self) -> StandardConstructor {
983        let length = PropertyDescriptor::builder()
984            .value(self.length)
985            .writable(false)
986            .enumerable(false)
987            .configurable(true);
988        let name = PropertyDescriptor::builder()
989            .value(self.name.clone())
990            .writable(false)
991            .enumerable(false)
992            .configurable(true);
993
994        let prototype = {
995            if let Some(proto) = self.inherit.take() {
996                self.prototype.set_prototype(proto);
997            } else {
998                self.prototype.set_prototype(
999                    self.context
1000                        .intrinsics()
1001                        .constructors()
1002                        .object()
1003                        .prototype(),
1004                );
1005            }
1006
1007            JsObject::from_object_and_vtable(self.prototype, &ORDINARY_INTERNAL_METHODS)
1008        };
1009
1010        let constructor = {
1011            let mut constructor = Object {
1012                properties: self.constructor_object.properties,
1013                extensible: self.constructor_object.extensible,
1014                private_elements: self.constructor_object.private_elements,
1015                data: NativeFunctionObject {
1016                    f: self.function,
1017                    constructor: self.kind,
1018                    realm: Some(self.context.realm().clone()),
1019                },
1020            };
1021
1022            constructor.insert(StaticJsStrings::LENGTH, length);
1023            constructor.insert(js_string!("name"), name);
1024
1025            if let Some(proto) = self.custom_prototype.take() {
1026                constructor.set_prototype(proto);
1027            } else {
1028                constructor.set_prototype(
1029                    self.context
1030                        .intrinsics()
1031                        .constructors()
1032                        .function()
1033                        .prototype(),
1034                );
1035            }
1036
1037            if self.has_prototype_property {
1038                constructor.insert(
1039                    PROTOTYPE,
1040                    PropertyDescriptor::builder()
1041                        .value(prototype.clone())
1042                        .writable(false)
1043                        .enumerable(false)
1044                        .configurable(false),
1045                );
1046            }
1047
1048            let internal_methods = constructor.data.internal_methods();
1049            JsObject::from_object_and_vtable(constructor, internal_methods)
1050        };
1051
1052        {
1053            let mut prototype = prototype.borrow_mut();
1054            prototype.insert(
1055                CONSTRUCTOR,
1056                PropertyDescriptor::builder()
1057                    .value(constructor.clone())
1058                    .writable(true)
1059                    .enumerable(false)
1060                    .configurable(true),
1061            );
1062        }
1063
1064        StandardConstructor::new(JsFunction::from_object_unchecked(constructor), prototype)
1065    }
1066}