Skip to main content

boa_engine/property/
mod.rs

1//! Boa's implementation of ECMAScript's Property Descriptor.
2//!
3//! The Property Descriptor type is used to explain the manipulation and reification of `Object`
4//! property attributes. Values of the Property Descriptor type are Records. Each field's name is
5//! an attribute name and its value is a corresponding attribute value as specified in
6//! [6.1.7.1][section]. In addition, any field may be present or absent. The schema name used
7//! within this specification to tag literal descriptions of Property Descriptor records is
8//! `PropertyDescriptor`.
9//!
10//! More information:
11//!  - [MDN documentation][mdn]
12//!  - [ECMAScript reference][spec]
13//!
14//! [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
15//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
16//! [section]: https://tc39.es/ecma262/#sec-property-attributes
17
18mod attribute;
19mod nonmaxu32;
20
21use crate::{
22    JsString, JsSymbol, JsValue, js_string, object::shape::slot::SlotAttributes, string::JsStr,
23};
24use boa_gc::{Finalize, Trace};
25use std::{fmt, iter::FusedIterator};
26
27pub use {attribute::Attribute, nonmaxu32::NonMaxU32};
28
29/// This represents an ECMAScript Property AKA The Property Descriptor.
30///
31/// Property descriptors present in objects come in three main flavors:
32///  - data descriptors
33///  - accessor descriptors
34///  - generic descriptor
35///
36/// A data Property Descriptor is one that includes any fields named either
37/// \[\[Value\]\] or \[\[Writable\]\].
38///
39/// An accessor Property Descriptor is one that includes any fields named either
40/// \[\[Get\]\] or \[\[Set\]\].
41///
42/// A generic Property Descriptor is a Property Descriptor value that is neither
43/// a data Property Descriptor nor an accessor Property Descriptor.
44///
45/// More information:
46/// - [MDN documentation][mdn]
47/// - [ECMAScript reference][spec]
48///
49/// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
50/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
51#[derive(Default, Debug, Clone, Trace, Finalize)]
52pub struct PropertyDescriptor {
53    enumerable: Option<bool>,
54    configurable: Option<bool>,
55    kind: DescriptorKind,
56}
57
58/// `DescriptorKind` represents the different kinds of property descriptors.
59#[derive(Debug, Default, Clone, Trace, Finalize)]
60pub enum DescriptorKind {
61    /// A data property descriptor.
62    Data {
63        /// The value of the property.
64        value: Option<JsValue>,
65
66        /// Whether the property is writable.
67        writable: Option<bool>,
68    },
69
70    /// An accessor property descriptor.
71    Accessor {
72        /// The getter of the property.
73        get: Option<JsValue>,
74
75        /// The setter of the property.
76        set: Option<JsValue>,
77    },
78
79    /// A generic property descriptor.
80    #[default]
81    Generic,
82}
83
84impl PropertyDescriptor {
85    /// An accessor property descriptor is one that includes any fields named either `[[Get]]` or `[[Set]]`.
86    ///
87    /// More information:
88    /// - [ECMAScript reference][spec]
89    ///
90    /// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor
91    #[inline]
92    #[must_use]
93    pub const fn is_accessor_descriptor(&self) -> bool {
94        matches!(self.kind, DescriptorKind::Accessor { .. })
95    }
96
97    /// A data property descriptor is one that includes any fields named either `[[Value]]` or `[[Writable]]`.
98    ///
99    /// More information:
100    /// - [ECMAScript reference][spec]
101    ///
102    /// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor
103    #[inline]
104    #[must_use]
105    pub const fn is_data_descriptor(&self) -> bool {
106        matches!(self.kind, DescriptorKind::Data { .. })
107    }
108
109    /// A generic property descriptor is one that is neither a data descriptor nor an accessor descriptor.
110    ///
111    /// More information:
112    /// - [ECMAScript reference][spec]
113    ///
114    /// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor
115    #[inline]
116    #[must_use]
117    pub const fn is_generic_descriptor(&self) -> bool {
118        matches!(self.kind, DescriptorKind::Generic)
119    }
120
121    /// Returns if the property descriptor is empty.
122    #[inline]
123    #[must_use]
124    pub const fn is_empty(&self) -> bool {
125        self.is_generic_descriptor() && self.enumerable.is_none() && self.configurable.is_none()
126    }
127
128    /// Returns if the property descriptor is enumerable.
129    /// Returns `None` if the `enumerable` field is not set.
130    #[inline]
131    #[must_use]
132    pub const fn enumerable(&self) -> Option<bool> {
133        self.enumerable
134    }
135
136    /// Returns if the property descriptor is configurable.
137    /// Returns `None` if the `configurable` field is not set.
138    #[inline]
139    #[must_use]
140    pub const fn configurable(&self) -> Option<bool> {
141        self.configurable
142    }
143
144    /// Returns if the property descriptor is writable.
145    /// Returns `None` if the `writable` field is not set or the property descriptor is not a data descriptor.
146    #[inline]
147    #[must_use]
148    pub const fn writable(&self) -> Option<bool> {
149        match self.kind {
150            DescriptorKind::Data { writable, .. } => writable,
151            _ => None,
152        }
153    }
154
155    /// Returns the value of the property descriptor.
156    /// Returns `None` if the value is not set or the property descriptor is not a data descriptor.
157    #[inline]
158    #[must_use]
159    pub const fn value(&self) -> Option<&JsValue> {
160        match &self.kind {
161            DescriptorKind::Data { value, .. } => value.as_ref(),
162            _ => None,
163        }
164    }
165
166    /// Returns the getter of the property descriptor.
167    /// Returns `None` if the getter is not set or the property descriptor is not an accessor descriptor.
168    #[inline]
169    #[must_use]
170    pub const fn get(&self) -> Option<&JsValue> {
171        match &self.kind {
172            DescriptorKind::Accessor { get, .. } => get.as_ref(),
173            _ => None,
174        }
175    }
176
177    /// Returns the setter of the property descriptor.
178    /// Returns `None` if the setter is not set or the property descriptor is not an accessor descriptor.
179    #[inline]
180    #[must_use]
181    pub const fn set(&self) -> Option<&JsValue> {
182        match &self.kind {
183            DescriptorKind::Accessor { set, .. } => set.as_ref(),
184            _ => None,
185        }
186    }
187
188    /// Returns if the property descriptor is enumerable.
189    ///
190    /// # Panics
191    ///
192    /// Panics if the `enumerable` field is not set.
193    #[inline]
194    #[must_use]
195    pub fn expect_enumerable(&self) -> bool {
196        self.enumerable
197            .expect("[[enumerable]] field not in property descriptor")
198    }
199
200    /// Returns if the property descriptor is configurable.
201    ///
202    /// # Panics
203    ///
204    /// Panics if the `configurable` field is not set.
205    #[inline]
206    #[must_use]
207    pub fn expect_configurable(&self) -> bool {
208        self.configurable
209            .expect("[[configurable]] field not in property descriptor")
210    }
211
212    /// Returns if the property descriptor is writable.
213    ///
214    /// # Panics
215    ///
216    /// Panics if the `writable` field is not set.
217    #[inline]
218    #[must_use]
219    pub fn expect_writable(&self) -> bool {
220        self.writable()
221            .expect("[[writable]] field not in property descriptor")
222    }
223
224    /// Returns the value of the property descriptor.
225    ///
226    /// # Panics
227    ///
228    /// Panics if the `value` field is not set.
229    #[inline]
230    #[must_use]
231    pub fn expect_value(&self) -> &JsValue {
232        self.value()
233            .expect("[[value]] field not in property descriptor")
234    }
235
236    /// Returns the getter of the property descriptor.
237    ///
238    /// # Panics
239    ///
240    /// Panics if the `getter` field is not set.
241    #[inline]
242    #[must_use]
243    pub fn expect_get(&self) -> &JsValue {
244        self.get()
245            .expect("[[get]] field not in property descriptor")
246    }
247
248    /// Returns the setter of the property descriptor.
249    ///
250    /// # Panics
251    ///
252    /// Panics if the `setter` field is not set.
253    #[inline]
254    #[must_use]
255    pub fn expect_set(&self) -> &JsValue {
256        self.set()
257            .expect("[[set]] field not in property descriptor")
258    }
259
260    /// Returns the kind of the property descriptor.
261    #[inline]
262    #[must_use]
263    pub const fn kind(&self) -> &DescriptorKind {
264        &self.kind
265    }
266
267    /// Creates a new [`PropertyDescriptorBuilder`].
268    #[inline]
269    #[must_use]
270    pub fn builder() -> PropertyDescriptorBuilder {
271        PropertyDescriptorBuilder::new()
272    }
273
274    /// Creates an accessor property descriptor with default values.
275    #[inline]
276    #[must_use]
277    pub fn into_accessor_defaulted(mut self) -> Self {
278        match &mut self.kind {
279            DescriptorKind::Accessor { set, get } => {
280                if set.is_none() {
281                    *set = Some(JsValue::undefined());
282                }
283                if get.is_none() {
284                    *get = Some(JsValue::undefined());
285                }
286            }
287            _ => {
288                self.kind = DescriptorKind::Accessor {
289                    get: Some(JsValue::undefined()),
290                    set: Some(JsValue::undefined()),
291                };
292            }
293        }
294        self.configurable = self.configurable.or(Some(false));
295        self.enumerable = self.enumerable.or(Some(false));
296        self
297    }
298
299    /// Creates a data property descriptor with default values.
300    #[must_use]
301    pub fn into_data_defaulted(mut self) -> Self {
302        match &mut self.kind {
303            DescriptorKind::Data { value, writable } => {
304                if value.is_none() {
305                    *value = Some(JsValue::undefined());
306                }
307                if writable.is_none() {
308                    *writable = Some(false);
309                }
310            }
311            _ => {
312                self.kind = DescriptorKind::Data {
313                    value: Some(JsValue::undefined()),
314                    writable: Some(false),
315                };
316            }
317        }
318        self.configurable = self.configurable.or(Some(false));
319        self.enumerable = self.enumerable.or(Some(false));
320        self
321    }
322
323    /// Creates an generic property descriptor with default values.
324    #[inline]
325    #[must_use]
326    pub fn complete_property_descriptor(self) -> Self {
327        PropertyDescriptorBuilder { inner: self }
328            .complete_with_defaults()
329            .build()
330    }
331
332    /// Fills the fields of the `PropertyDescriptor` that are not set
333    /// with fields from the given `PropertyDescriptor`.
334    ///
335    /// # Panics
336    ///
337    /// Panics if the given `PropertyDescriptor` is not compatible with this one.
338    #[inline]
339    pub fn fill_with(&mut self, mut desc: Self) {
340        match (&mut self.kind, &mut desc.kind) {
341            (
342                DescriptorKind::Data { value, writable },
343                DescriptorKind::Data {
344                    value: desc_value,
345                    writable: desc_writable,
346                },
347            ) => {
348                if desc_value.is_some() {
349                    std::mem::swap(value, desc_value);
350                }
351                if desc_writable.is_some() {
352                    std::mem::swap(writable, desc_writable);
353                }
354            }
355            (
356                DescriptorKind::Accessor { get, set },
357                DescriptorKind::Accessor {
358                    get: desc_get,
359                    set: desc_set,
360                },
361            ) => {
362                if desc_get.is_some() {
363                    std::mem::swap(get, desc_get);
364                }
365                if desc_set.is_some() {
366                    std::mem::swap(set, desc_set);
367                }
368            }
369            (_, DescriptorKind::Generic) => {}
370            _ => panic!("Tried to fill a descriptor with an incompatible descriptor"),
371        }
372
373        if let Some(enumerable) = desc.enumerable {
374            self.enumerable = Some(enumerable);
375        }
376        if let Some(configurable) = desc.configurable {
377            self.configurable = Some(configurable);
378        }
379    }
380
381    pub(crate) fn to_slot_attributes(&self) -> SlotAttributes {
382        let mut attributes = SlotAttributes::empty();
383        attributes.set(SlotAttributes::CONFIGURABLE, self.expect_configurable());
384        attributes.set(SlotAttributes::ENUMERABLE, self.expect_enumerable());
385        if self.is_data_descriptor() {
386            attributes.set(SlotAttributes::WRITABLE, self.expect_writable());
387        } else {
388            attributes.set(SlotAttributes::GET, self.get().is_some());
389            attributes.set(SlotAttributes::SET, self.set().is_some());
390        }
391        attributes
392    }
393}
394
395/// A builder for [`PropertyDescriptor`].
396#[derive(Default, Debug, Clone)]
397pub struct PropertyDescriptorBuilder {
398    inner: PropertyDescriptor,
399}
400
401impl PropertyDescriptorBuilder {
402    /// Creates a new [`PropertyDescriptorBuilder`].
403    #[must_use]
404    pub fn new() -> Self {
405        Self::default()
406    }
407
408    /// Sets the `value` field of the property descriptor.
409    #[must_use]
410    pub fn value<V: Into<JsValue>>(mut self, value: V) -> Self {
411        match self.inner.kind {
412            DescriptorKind::Data {
413                value: ref mut v, ..
414            } => *v = Some(value.into()),
415            // TODO: maybe panic when trying to convert accessor to data?
416            _ => {
417                self.inner.kind = DescriptorKind::Data {
418                    value: Some(value.into()),
419                    writable: None,
420                }
421            }
422        }
423        self
424    }
425
426    /// Sets the `writable` field of the property descriptor.
427    #[must_use]
428    pub fn writable(mut self, writable: bool) -> Self {
429        match self.inner.kind {
430            DescriptorKind::Data {
431                writable: ref mut w,
432                ..
433            } => *w = Some(writable),
434            // TODO: maybe panic when trying to convert accessor to data?
435            _ => {
436                self.inner.kind = DescriptorKind::Data {
437                    value: None,
438                    writable: Some(writable),
439                }
440            }
441        }
442        self
443    }
444
445    /// Sets the `get` field of the property descriptor.
446    #[must_use]
447    pub fn get<V: Into<JsValue>>(mut self, get: V) -> Self {
448        match self.inner.kind {
449            DescriptorKind::Accessor { get: ref mut g, .. } => *g = Some(get.into()),
450            // TODO: maybe panic when trying to convert data to accessor?
451            _ => {
452                self.inner.kind = DescriptorKind::Accessor {
453                    get: Some(get.into()),
454                    set: None,
455                }
456            }
457        }
458        self
459    }
460
461    /// Sets the `set` field of the property descriptor.
462    #[must_use]
463    pub fn set<V: Into<JsValue>>(mut self, set: V) -> Self {
464        match self.inner.kind {
465            DescriptorKind::Accessor { set: ref mut s, .. } => *s = Some(set.into()),
466            // TODO: maybe panic when trying to convert data to accessor?
467            _ => {
468                self.inner.kind = DescriptorKind::Accessor {
469                    set: Some(set.into()),
470                    get: None,
471                }
472            }
473        }
474        self
475    }
476
477    /// Optionally sets the `enumerable` field of the property descriptor.
478    #[must_use]
479    pub const fn maybe_enumerable(mut self, enumerable: Option<bool>) -> Self {
480        if let Some(enumerable) = enumerable {
481            self = self.enumerable(enumerable);
482        }
483        self
484    }
485
486    /// Optionally sets the `configurable` field of the property descriptor.
487    #[must_use]
488    pub const fn maybe_configurable(mut self, configurable: Option<bool>) -> Self {
489        if let Some(configurable) = configurable {
490            self = self.configurable(configurable);
491        }
492        self
493    }
494
495    /// Optionally sets the `value` field of the property descriptor.
496    #[must_use]
497    pub fn maybe_value<V: Into<JsValue>>(mut self, value: Option<V>) -> Self {
498        if let Some(value) = value {
499            self = self.value(value);
500        }
501        self
502    }
503
504    /// Optionally sets the `writable` field of the property descriptor.
505    #[must_use]
506    pub fn maybe_writable(mut self, writable: Option<bool>) -> Self {
507        if let Some(writable) = writable {
508            self = self.writable(writable);
509        }
510        self
511    }
512
513    /// Optionally sets the `get` field of the property descriptor.
514    #[must_use]
515    pub fn maybe_get<V: Into<JsValue>>(mut self, get: Option<V>) -> Self {
516        if let Some(get) = get {
517            self = self.get(get);
518        }
519        self
520    }
521
522    /// Optionally sets the `set` field of the property descriptor.
523    #[must_use]
524    pub fn maybe_set<V: Into<JsValue>>(mut self, set: Option<V>) -> Self {
525        if let Some(set) = set {
526            self = self.set(set);
527        }
528        self
529    }
530
531    /// Sets the `enumerable` field of the property descriptor.
532    #[must_use]
533    pub const fn enumerable(mut self, enumerable: bool) -> Self {
534        self.inner.enumerable = Some(enumerable);
535        self
536    }
537
538    /// Sets the `configurable` field of the property descriptor.
539    #[must_use]
540    pub const fn configurable(mut self, configurable: bool) -> Self {
541        self.inner.configurable = Some(configurable);
542        self
543    }
544
545    /// Fill any missing fields in the property descriptor.
546    #[must_use]
547    pub fn complete_with_defaults(mut self) -> Self {
548        match self.inner.kind {
549            DescriptorKind::Generic => {
550                self.inner.kind = DescriptorKind::Data {
551                    value: Some(JsValue::undefined()),
552                    writable: Some(false),
553                }
554            }
555            DescriptorKind::Data {
556                ref mut value,
557                ref mut writable,
558            } => {
559                if value.is_none() {
560                    *value = Some(JsValue::undefined());
561                }
562                if writable.is_none() {
563                    *writable = Some(false);
564                }
565            }
566            DescriptorKind::Accessor {
567                ref mut set,
568                ref mut get,
569            } => {
570                if set.is_none() {
571                    *set = Some(JsValue::undefined());
572                }
573                if get.is_none() {
574                    *get = Some(JsValue::undefined());
575                }
576            }
577        }
578        if self.inner.configurable.is_none() {
579            self.inner.configurable = Some(false);
580        }
581        if self.inner.enumerable.is_none() {
582            self.inner.enumerable = Some(false);
583        }
584        self
585    }
586
587    /// Returns a reference to the currently built [`PropertyDescriptor`].
588    #[must_use]
589    pub const fn inner(&self) -> &PropertyDescriptor {
590        &self.inner
591    }
592
593    /// Consumes the builder and returns the [`PropertyDescriptor`].
594    #[must_use]
595    #[allow(clippy::missing_const_for_fn)]
596    pub fn build(self) -> PropertyDescriptor {
597        self.inner
598    }
599}
600
601impl From<PropertyDescriptorBuilder> for PropertyDescriptor {
602    fn from(builder: PropertyDescriptorBuilder) -> Self {
603        builder.build()
604    }
605}
606
607/// This abstracts away the need for `IsPropertyKey` by transforming the `PropertyKey`
608/// values into an enum with both valid types: String and Symbol
609///
610/// More information:
611/// - [ECMAScript reference][spec]
612///
613/// [spec]: https://tc39.es/ecma262/#sec-ispropertykey
614#[derive(Finalize, PartialEq, Debug, Clone, Eq, Hash)]
615pub enum PropertyKey {
616    /// A string property key.
617    String(JsString),
618
619    /// A symbol property key.
620    Symbol(JsSymbol),
621
622    /// A numeric property key.
623    Index(NonMaxU32),
624}
625
626/// Utility function for parsing [`PropertyKey`].
627fn parse_u32_index<I, T>(mut input: I) -> Option<NonMaxU32>
628where
629    I: Iterator<Item = T> + ExactSizeIterator + FusedIterator,
630    T: Into<u16>,
631{
632    // min: 0             --> 1  char
633    // max: 4_294_967_296 --> 10 chars
634    //
635    // Max char range: [1, 10] inclusive.
636    const MAX_CHAR_COUNT: usize = 10;
637
638    const CHAR_ZERO: u16 = b'0' as u16;
639    const CHAR_NINE: u16 = b'9' as u16;
640
641    // Eliminate any string if it's greater than the max char count.
642    let len = input.len();
643    if len > MAX_CHAR_COUNT {
644        return None;
645    }
646
647    // Helper function, for converting character to digit [0, 9].
648    let to_digit = |c: u16| -> Option<u32> {
649        if matches!(c, CHAR_ZERO..=CHAR_NINE) {
650            Some(u32::from(c - CHAR_ZERO))
651        } else {
652            None
653        }
654    };
655
656    let byte = input.next()?.into();
657    if byte == CHAR_ZERO {
658        if len == 1 {
659            // SAFETY: `0` is not `u32::MAX`.
660            return unsafe { Some(NonMaxU32::new_unchecked(0)) };
661        }
662
663        // String "012345" is not a valid index.
664        return None;
665    }
666
667    let mut result = to_digit(byte)?;
668
669    // If the len is equal to max chars, then we need to do checked operations,
670    // in case of overflows. If less use unchecked versions.
671    if len == MAX_CHAR_COUNT {
672        for c in input {
673            result = result.checked_mul(10)?.checked_add(to_digit(c.into())?)?;
674        }
675
676        NonMaxU32::new(result)
677    } else {
678        for c in input {
679            result = result * 10 + to_digit(c.into())?;
680        }
681
682        // SAFETY: `result` cannot be `u32::MAX`,
683        //         because the length of the input is smaller than `MAX_CHAR_COUNT`.
684        unsafe { Some(NonMaxU32::new_unchecked(result)) }
685    }
686}
687
688impl From<JsStr<'_>> for PropertyKey {
689    #[inline]
690    fn from(string: JsStr<'_>) -> Self {
691        parse_u32_index(string.iter()).map_or_else(|| Self::String(string.into()), Self::Index)
692    }
693}
694
695impl From<JsString> for PropertyKey {
696    #[inline]
697    fn from(string: JsString) -> Self {
698        parse_u32_index(string.as_str().iter()).map_or(Self::String(string), Self::Index)
699    }
700}
701
702impl From<JsSymbol> for PropertyKey {
703    #[inline]
704    fn from(symbol: JsSymbol) -> Self {
705        Self::Symbol(symbol)
706    }
707}
708
709impl fmt::Display for PropertyKey {
710    #[inline]
711    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712        match self {
713            Self::String(string) => string.to_std_string_escaped().fmt(f),
714            Self::Symbol(symbol) => symbol.descriptive_string().to_std_string_escaped().fmt(f),
715            Self::Index(index) => index.get().fmt(f),
716        }
717    }
718}
719
720impl From<&PropertyKey> for JsValue {
721    #[inline]
722    fn from(property_key: &PropertyKey) -> Self {
723        match property_key {
724            PropertyKey::String(string) => string.clone().into(),
725            PropertyKey::Symbol(symbol) => symbol.clone().into(),
726            PropertyKey::Index(index) => {
727                i32::try_from(index.get()).map_or_else(|_| Self::new(index.get()), Self::new)
728            }
729        }
730    }
731}
732
733impl From<PropertyKey> for JsValue {
734    #[inline]
735    fn from(property_key: PropertyKey) -> Self {
736        match property_key {
737            PropertyKey::String(ref string) => string.clone().into(),
738            PropertyKey::Symbol(ref symbol) => symbol.clone().into(),
739            PropertyKey::Index(index) => js_string!(index.get()).into(),
740        }
741    }
742}
743
744impl From<u8> for PropertyKey {
745    fn from(value: u8) -> Self {
746        // SAFETY: `u8` can never be `u32::MAX`.
747        unsafe { Self::Index(NonMaxU32::new_unchecked(value.into())) }
748    }
749}
750
751impl From<u16> for PropertyKey {
752    fn from(value: u16) -> Self {
753        // SAFETY: `u16` can never be `u32::MAX`.
754        unsafe { Self::Index(NonMaxU32::new_unchecked(value.into())) }
755    }
756}
757
758impl From<u32> for PropertyKey {
759    fn from(value: u32) -> Self {
760        NonMaxU32::new(value).map_or_else(|| Self::String(value.into()), Self::Index)
761    }
762}
763
764impl From<usize> for PropertyKey {
765    fn from(value: usize) -> Self {
766        u32::try_from(value)
767            .ok()
768            .and_then(NonMaxU32::new)
769            .map_or_else(|| Self::String(value.into()), Self::Index)
770    }
771}
772
773impl From<i64> for PropertyKey {
774    fn from(value: i64) -> Self {
775        u32::try_from(value)
776            .ok()
777            .and_then(NonMaxU32::new)
778            .map_or_else(|| Self::String(value.into()), Self::Index)
779    }
780}
781
782impl From<u64> for PropertyKey {
783    fn from(value: u64) -> Self {
784        u32::try_from(value)
785            .ok()
786            .and_then(NonMaxU32::new)
787            .map_or_else(|| Self::String(value.into()), Self::Index)
788    }
789}
790
791impl From<isize> for PropertyKey {
792    fn from(value: isize) -> Self {
793        u32::try_from(value)
794            .ok()
795            .and_then(NonMaxU32::new)
796            .map_or_else(|| Self::String(value.into()), Self::Index)
797    }
798}
799
800impl From<i32> for PropertyKey {
801    fn from(value: i32) -> Self {
802        if !value.is_negative() {
803            // Safety: A positive i32 value fits in 31 bits, so it can never be u32::MAX.
804            return Self::Index(unsafe { NonMaxU32::new_unchecked(value as u32) });
805        }
806        Self::String(value.into())
807    }
808}
809
810impl From<f64> for PropertyKey {
811    fn from(value: f64) -> Self {
812        use num_traits::cast::FromPrimitive;
813
814        u32::from_f64(value)
815            .and_then(NonMaxU32::new)
816            .map_or_else(|| Self::String(value.into()), Self::Index)
817    }
818}
819
820impl PartialEq<[u16]> for PropertyKey {
821    fn eq(&self, other: &[u16]) -> bool {
822        match self {
823            Self::String(string) => string == other,
824            _ => false,
825        }
826    }
827}
828
829#[derive(Debug, Clone, Copy)]
830pub(crate) enum PropertyNameKind {
831    Key,
832    Value,
833    KeyAndValue,
834}