boa/property/
mod.rs

1//! This module implements the Property Descriptor.
2//!
3//! The Property Descriptor type is used to explain the manipulation and reification of Object property attributes.
4//! Values of the Property Descriptor type are Records. Each field's name is an attribute name
5//! and its value is a corresponding attribute value as specified in [6.1.7.1][section].
6//! In addition, any field may be present or absent.
7//! The schema name used within this specification to tag literal descriptions of Property Descriptor records is “PropertyDescriptor”.
8//!
9//! More information:
10//!  - [MDN documentation][mdn]
11//!  - [ECMAScript reference][spec]
12//!
13//! [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
14//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
15//! [section]: https://tc39.es/ecma262/#sec-property-attributes
16
17use crate::{
18    gc::{Finalize, Trace},
19    JsString, JsSymbol, JsValue,
20};
21use std::{convert::TryFrom, fmt};
22
23mod attribute;
24pub use attribute::Attribute;
25use gc::unsafe_empty_trace;
26
27/// This represents a JavaScript Property AKA The Property Descriptor.
28///
29/// Property descriptors present in objects come in three main flavors:
30///  - data descriptors
31///  - accessor descriptors
32///  - generic descriptor
33///
34/// A data Property Descriptor is one that includes any fields named either
35/// \[\[Value\]\] or \[\[Writable\]\].
36///
37/// An accessor Property Descriptor is one that includes any fields named either
38/// \[\[Get\]\] or \[\[Set\]\].
39///
40/// A generic Property Descriptor is a Property Descriptor value that is neither
41/// a data Property Descriptor nor an accessor Property Descriptor.
42///
43/// More information:
44/// - [MDN documentation][mdn]
45/// - [ECMAScript reference][spec]
46///
47/// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
48/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
49#[derive(Default, Debug, Clone, Trace, Finalize)]
50pub struct PropertyDescriptor {
51    enumerable: Option<bool>,
52    configurable: Option<bool>,
53    kind: DescriptorKind,
54}
55
56#[derive(Debug, Clone, Trace, Finalize)]
57pub enum DescriptorKind {
58    Data {
59        value: Option<JsValue>,
60        writable: Option<bool>,
61    },
62    Accessor {
63        get: Option<JsValue>,
64        set: Option<JsValue>,
65    },
66    Generic,
67}
68
69impl Default for DescriptorKind {
70    fn default() -> Self {
71        Self::Generic
72    }
73}
74
75impl PropertyDescriptor {
76    /// An accessor Property Descriptor is one that includes any fields named either `[[Get]]` or `[[Set]]`.
77    ///
78    /// More information:
79    /// - [ECMAScript reference][spec]
80    ///
81    /// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor
82    #[inline]
83    pub fn is_accessor_descriptor(&self) -> bool {
84        matches!(self.kind, DescriptorKind::Accessor { .. })
85    }
86
87    /// A data Property Descriptor is one that includes any fields named either `[[Value]]` or `[[Writable]]`.
88    ///
89    /// More information:
90    /// - [ECMAScript reference][spec]
91    ///
92    /// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor
93    #[inline]
94    pub fn is_data_descriptor(&self) -> bool {
95        matches!(self.kind, DescriptorKind::Data { .. })
96    }
97
98    /// A generic Property Descriptor is one that is neither a data descriptor nor an accessor descriptor.
99    ///
100    /// More information:
101    /// - [ECMAScript reference][spec]
102    ///
103    /// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor
104    #[inline]
105    pub fn is_generic_descriptor(&self) -> bool {
106        matches!(self.kind, DescriptorKind::Generic)
107    }
108
109    #[inline]
110    pub fn is_empty(&self) -> bool {
111        self.is_generic_descriptor() && self.enumerable.is_none() && self.configurable.is_none()
112    }
113
114    #[inline]
115    pub fn enumerable(&self) -> Option<bool> {
116        self.enumerable
117    }
118
119    #[inline]
120    pub fn configurable(&self) -> Option<bool> {
121        self.configurable
122    }
123
124    #[inline]
125    pub fn writable(&self) -> Option<bool> {
126        match self.kind {
127            DescriptorKind::Data { writable, .. } => writable,
128            _ => None,
129        }
130    }
131
132    #[inline]
133    pub fn value(&self) -> Option<&JsValue> {
134        match &self.kind {
135            DescriptorKind::Data { value, .. } => value.as_ref(),
136            _ => None,
137        }
138    }
139
140    #[inline]
141    pub fn get(&self) -> Option<&JsValue> {
142        match &self.kind {
143            DescriptorKind::Accessor { get, .. } => get.as_ref(),
144            _ => None,
145        }
146    }
147
148    #[inline]
149    pub fn set(&self) -> Option<&JsValue> {
150        match &self.kind {
151            DescriptorKind::Accessor { set, .. } => set.as_ref(),
152            _ => None,
153        }
154    }
155
156    #[inline]
157    pub fn expect_enumerable(&self) -> bool {
158        if let Some(enumerable) = self.enumerable {
159            enumerable
160        } else {
161            panic!("[[enumerable]] field not in property descriptor")
162        }
163    }
164
165    #[inline]
166    pub fn expect_configurable(&self) -> bool {
167        if let Some(configurable) = self.configurable {
168            configurable
169        } else {
170            panic!("[[configurable]] field not in property descriptor")
171        }
172    }
173
174    #[inline]
175    pub fn expect_writable(&self) -> bool {
176        if let Some(writable) = self.writable() {
177            writable
178        } else {
179            panic!("[[writable]] field not in property descriptor")
180        }
181    }
182
183    #[inline]
184    pub fn expect_value(&self) -> &JsValue {
185        if let Some(value) = self.value() {
186            value
187        } else {
188            panic!("[[value]] field not in property descriptor")
189        }
190    }
191
192    #[inline]
193    pub fn expect_get(&self) -> &JsValue {
194        if let Some(get) = self.get() {
195            get
196        } else {
197            panic!("[[get]] field not in property descriptor")
198        }
199    }
200
201    #[inline]
202    pub fn expect_set(&self) -> &JsValue {
203        if let Some(set) = self.set() {
204            set
205        } else {
206            panic!("[[set]] field not in property descriptor")
207        }
208    }
209
210    #[inline]
211    pub fn kind(&self) -> &DescriptorKind {
212        &self.kind
213    }
214
215    #[inline]
216    pub fn builder() -> PropertyDescriptorBuilder {
217        PropertyDescriptorBuilder::new()
218    }
219
220    #[inline]
221    pub fn into_accessor_defaulted(mut self) -> Self {
222        self.kind = DescriptorKind::Accessor {
223            get: self.get().cloned(),
224            set: self.set().cloned(),
225        };
226        PropertyDescriptorBuilder { inner: self }
227            .complete_with_defaults()
228            .build()
229    }
230
231    pub fn into_data_defaulted(mut self) -> Self {
232        self.kind = DescriptorKind::Data {
233            value: self.value().cloned(),
234            writable: self.writable(),
235        };
236        PropertyDescriptorBuilder { inner: self }
237            .complete_with_defaults()
238            .build()
239    }
240
241    #[inline]
242    pub fn complete_property_descriptor(self) -> Self {
243        PropertyDescriptorBuilder { inner: self }
244            .complete_with_defaults()
245            .build()
246    }
247
248    #[inline]
249    pub fn fill_with(&mut self, desc: Self) {
250        match (&mut self.kind, &desc.kind) {
251            (
252                DescriptorKind::Data { value, writable },
253                DescriptorKind::Data {
254                    value: desc_value,
255                    writable: desc_writable,
256                },
257            ) => {
258                if let Some(desc_value) = desc_value {
259                    *value = Some(desc_value.clone())
260                }
261                if let Some(desc_writable) = desc_writable {
262                    *writable = Some(*desc_writable)
263                }
264            }
265            (
266                DescriptorKind::Accessor { get, set },
267                DescriptorKind::Accessor {
268                    get: desc_get,
269                    set: desc_set,
270                },
271            ) => {
272                if let Some(desc_get) = desc_get {
273                    *get = Some(desc_get.clone())
274                }
275                if let Some(desc_set) = desc_set {
276                    *set = Some(desc_set.clone())
277                }
278            }
279            (_, DescriptorKind::Generic) => {}
280            _ => panic!("Tried to fill a descriptor with an incompatible descriptor"),
281        }
282
283        if let Some(enumerable) = desc.enumerable {
284            self.enumerable = Some(enumerable)
285        }
286        if let Some(configurable) = desc.configurable {
287            self.configurable = Some(configurable)
288        }
289    }
290}
291
292#[derive(Default, Debug, Clone)]
293pub struct PropertyDescriptorBuilder {
294    inner: PropertyDescriptor,
295}
296
297impl PropertyDescriptorBuilder {
298    pub fn new() -> Self {
299        Self::default()
300    }
301
302    pub fn value<V: Into<JsValue>>(mut self, value: V) -> Self {
303        match self.inner.kind {
304            DescriptorKind::Data {
305                value: ref mut v, ..
306            } => *v = Some(value.into()),
307            // TODO: maybe panic when trying to convert accessor to data?
308            _ => {
309                self.inner.kind = DescriptorKind::Data {
310                    value: Some(value.into()),
311                    writable: None,
312                }
313            }
314        }
315        self
316    }
317
318    pub fn writable(mut self, writable: bool) -> Self {
319        match self.inner.kind {
320            DescriptorKind::Data {
321                writable: ref mut w,
322                ..
323            } => *w = Some(writable),
324            // TODO: maybe panic when trying to convert accessor to data?
325            _ => {
326                self.inner.kind = DescriptorKind::Data {
327                    value: None,
328                    writable: Some(writable),
329                }
330            }
331        }
332        self
333    }
334
335    pub fn get<V: Into<JsValue>>(mut self, get: V) -> Self {
336        match self.inner.kind {
337            DescriptorKind::Accessor { get: ref mut g, .. } => *g = Some(get.into()),
338            // TODO: maybe panic when trying to convert data to accessor?
339            _ => {
340                self.inner.kind = DescriptorKind::Accessor {
341                    get: Some(get.into()),
342                    set: None,
343                }
344            }
345        }
346        self
347    }
348
349    pub fn set<V: Into<JsValue>>(mut self, set: V) -> Self {
350        match self.inner.kind {
351            DescriptorKind::Accessor { set: ref mut s, .. } => *s = Some(set.into()),
352            // TODO: maybe panic when trying to convert data to accessor?
353            _ => {
354                self.inner.kind = DescriptorKind::Accessor {
355                    set: Some(set.into()),
356                    get: None,
357                }
358            }
359        }
360        self
361    }
362
363    pub fn maybe_enumerable(mut self, enumerable: Option<bool>) -> Self {
364        if let Some(enumerable) = enumerable {
365            self = self.enumerable(enumerable);
366        }
367        self
368    }
369
370    pub fn maybe_configurable(mut self, configurable: Option<bool>) -> Self {
371        if let Some(configurable) = configurable {
372            self = self.configurable(configurable);
373        }
374        self
375    }
376
377    pub fn maybe_value<V: Into<JsValue>>(mut self, value: Option<V>) -> Self {
378        if let Some(value) = value {
379            self = self.value(value);
380        }
381        self
382    }
383
384    pub fn maybe_writable(mut self, writable: Option<bool>) -> Self {
385        if let Some(writable) = writable {
386            self = self.writable(writable);
387        }
388        self
389    }
390
391    pub fn maybe_get<V: Into<JsValue>>(mut self, get: Option<V>) -> Self {
392        if let Some(get) = get {
393            self = self.get(get);
394        }
395        self
396    }
397
398    pub fn maybe_set<V: Into<JsValue>>(mut self, set: Option<V>) -> Self {
399        if let Some(set) = set {
400            self = self.set(set);
401        }
402        self
403    }
404
405    pub fn enumerable(mut self, enumerable: bool) -> Self {
406        self.inner.enumerable = Some(enumerable);
407        self
408    }
409    pub fn configurable(mut self, configurable: bool) -> Self {
410        self.inner.configurable = Some(configurable);
411        self
412    }
413
414    pub fn complete_with_defaults(mut self) -> Self {
415        match self.inner.kind {
416            DescriptorKind::Generic => {
417                self.inner.kind = DescriptorKind::Data {
418                    value: Some(JsValue::undefined()),
419                    writable: Some(false),
420                }
421            }
422            DescriptorKind::Data {
423                ref mut value,
424                ref mut writable,
425            } => {
426                if value.is_none() {
427                    *value = Some(JsValue::undefined())
428                }
429                if writable.is_none() {
430                    *writable = Some(false)
431                }
432            }
433            DescriptorKind::Accessor {
434                ref mut set,
435                ref mut get,
436            } => {
437                if set.is_none() {
438                    *set = Some(JsValue::undefined())
439                }
440                if get.is_none() {
441                    *get = Some(JsValue::undefined())
442                }
443            }
444        }
445        if self.inner.configurable.is_none() {
446            self.inner.configurable = Some(false);
447        }
448        if self.inner.enumerable.is_none() {
449            self.inner.enumerable = Some(false);
450        }
451        self
452    }
453
454    pub fn inner(&self) -> &PropertyDescriptor {
455        &self.inner
456    }
457
458    pub fn build(self) -> PropertyDescriptor {
459        self.inner
460    }
461}
462
463impl From<PropertyDescriptorBuilder> for PropertyDescriptor {
464    fn from(builder: PropertyDescriptorBuilder) -> Self {
465        builder.build()
466    }
467}
468
469/// This abstracts away the need for IsPropertyKey by transforming the PropertyKey
470/// values into an enum with both valid types: String and Symbol
471///
472/// More information:
473/// - [ECMAScript reference][spec]
474///
475/// [spec]: https://tc39.es/ecma262/#sec-ispropertykey
476#[derive(Trace, Finalize, PartialEq, Debug, Clone)]
477pub enum PropertyKey {
478    String(JsString),
479    Symbol(JsSymbol),
480    Index(u32),
481}
482
483impl From<JsString> for PropertyKey {
484    #[inline]
485    fn from(string: JsString) -> PropertyKey {
486        if let Ok(index) = string.parse() {
487            PropertyKey::Index(index)
488        } else {
489            PropertyKey::String(string)
490        }
491    }
492}
493
494impl From<&str> for PropertyKey {
495    #[inline]
496    fn from(string: &str) -> PropertyKey {
497        if let Ok(index) = string.parse() {
498            PropertyKey::Index(index)
499        } else {
500            PropertyKey::String(string.into())
501        }
502    }
503}
504
505impl From<String> for PropertyKey {
506    #[inline]
507    fn from(string: String) -> PropertyKey {
508        if let Ok(index) = string.parse() {
509            PropertyKey::Index(index)
510        } else {
511            PropertyKey::String(string.into())
512        }
513    }
514}
515
516impl From<Box<str>> for PropertyKey {
517    #[inline]
518    fn from(string: Box<str>) -> PropertyKey {
519        if let Ok(index) = string.parse() {
520            PropertyKey::Index(index)
521        } else {
522            PropertyKey::String(string.into())
523        }
524    }
525}
526
527impl From<JsSymbol> for PropertyKey {
528    #[inline]
529    fn from(symbol: JsSymbol) -> PropertyKey {
530        PropertyKey::Symbol(symbol)
531    }
532}
533
534impl fmt::Display for PropertyKey {
535    #[inline]
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        match self {
538            PropertyKey::String(ref string) => string.fmt(f),
539            PropertyKey::Symbol(ref symbol) => symbol.fmt(f),
540            PropertyKey::Index(index) => index.fmt(f),
541        }
542    }
543}
544
545impl From<&PropertyKey> for JsValue {
546    #[inline]
547    fn from(property_key: &PropertyKey) -> JsValue {
548        match property_key {
549            PropertyKey::String(ref string) => string.clone().into(),
550            PropertyKey::Symbol(ref symbol) => symbol.clone().into(),
551            PropertyKey::Index(index) => {
552                if let Ok(integer) = i32::try_from(*index) {
553                    JsValue::new(integer)
554                } else {
555                    JsValue::new(*index)
556                }
557            }
558        }
559    }
560}
561
562impl From<PropertyKey> for JsValue {
563    #[inline]
564    fn from(property_key: PropertyKey) -> JsValue {
565        match property_key {
566            PropertyKey::String(ref string) => string.clone().into(),
567            PropertyKey::Symbol(ref symbol) => symbol.clone().into(),
568            PropertyKey::Index(index) => {
569                if let Ok(integer) = i32::try_from(index) {
570                    JsValue::new(integer)
571                } else {
572                    JsValue::new(index)
573                }
574            }
575        }
576    }
577}
578
579impl From<u8> for PropertyKey {
580    fn from(value: u8) -> Self {
581        PropertyKey::Index(value.into())
582    }
583}
584
585impl From<u16> for PropertyKey {
586    fn from(value: u16) -> Self {
587        PropertyKey::Index(value.into())
588    }
589}
590
591impl From<u32> for PropertyKey {
592    fn from(value: u32) -> Self {
593        PropertyKey::Index(value)
594    }
595}
596
597impl From<usize> for PropertyKey {
598    fn from(value: usize) -> Self {
599        if let Ok(index) = u32::try_from(value) {
600            PropertyKey::Index(index)
601        } else {
602            PropertyKey::String(JsString::from(value.to_string()))
603        }
604    }
605}
606
607impl From<i64> for PropertyKey {
608    fn from(value: i64) -> Self {
609        if let Ok(index) = u32::try_from(value) {
610            PropertyKey::Index(index)
611        } else {
612            PropertyKey::String(JsString::from(value.to_string()))
613        }
614    }
615}
616
617impl From<u64> for PropertyKey {
618    fn from(value: u64) -> Self {
619        if let Ok(index) = u32::try_from(value) {
620            PropertyKey::Index(index)
621        } else {
622            PropertyKey::String(JsString::from(value.to_string()))
623        }
624    }
625}
626
627impl From<isize> for PropertyKey {
628    fn from(value: isize) -> Self {
629        if let Ok(index) = u32::try_from(value) {
630            PropertyKey::Index(index)
631        } else {
632            PropertyKey::String(JsString::from(value.to_string()))
633        }
634    }
635}
636
637impl From<i32> for PropertyKey {
638    fn from(value: i32) -> Self {
639        if let Ok(index) = u32::try_from(value) {
640            PropertyKey::Index(index)
641        } else {
642            PropertyKey::String(JsString::from(value.to_string()))
643        }
644    }
645}
646
647impl From<f64> for PropertyKey {
648    fn from(value: f64) -> Self {
649        use num_traits::cast::FromPrimitive;
650        if let Some(index) = u32::from_f64(value) {
651            return PropertyKey::Index(index);
652        }
653
654        PropertyKey::String(ryu_js::Buffer::new().format(value).into())
655    }
656}
657
658impl PartialEq<&str> for PropertyKey {
659    fn eq(&self, other: &&str) -> bool {
660        match self {
661            PropertyKey::String(ref string) => string == other,
662            _ => false,
663        }
664    }
665}
666
667#[derive(Debug, Clone, Copy, Finalize)]
668pub(crate) enum PropertyNameKind {
669    Key,
670    Value,
671    KeyAndValue,
672}
673
674unsafe impl Trace for PropertyNameKind {
675    unsafe_empty_trace!();
676}