Skip to main content

quickjs_oxide/engine/api/core/
property.rs

1//! Property keys and descriptors.
2
3use super::RuntimeError;
4use super::value::{CallableRef, SymbolRef, Value};
5use crate::engine::value::JsString;
6
7/// A property key: a string, an array index or a symbol. Made by
8/// [`Runtime::intern_property_key`](super::Runtime::intern_property_key) or
9/// from a [`SymbolRef`].
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct PropertyKey(pub(crate) Key);
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub(crate) enum Key {
15    String(JsString),
16    Index(u32),
17    Symbol(SymbolRef),
18}
19
20impl PropertyKey {
21    pub fn try_clone(&self) -> Result<Self, RuntimeError> {
22        Ok(self.clone())
23    }
24
25    /// The key of the string `text` (an index when `text` is one's
26    /// canonical form, as `ToPropertyKey` reads it).
27    pub fn from_js_string(text: JsString) -> Self {
28        let units: Vec<u16> = text.utf16_units().collect();
29        match canonical_index(&units) {
30            Some(index) => PropertyKey(Key::Index(index)),
31            None => PropertyKey(Key::String(text)),
32        }
33    }
34
35    pub fn index(index: u32) -> Self {
36        if index < u32::MAX {
37            PropertyKey(Key::Index(index))
38        } else {
39            PropertyKey(Key::String(JsString::from_fresh_decimal_u32(index)))
40        }
41    }
42
43    pub fn as_symbol(&self) -> Option<&SymbolRef> {
44        match &self.0 {
45            Key::Symbol(symbol) => Some(symbol),
46            _ => None,
47        }
48    }
49
50    /// The key as a string; `None` for a symbol.
51    pub fn to_js_string(&self) -> Option<JsString> {
52        match &self.0 {
53            Key::String(text) => Some(text.clone()),
54            Key::Index(index) => Some(JsString::from_fresh_decimal_u32(*index)),
55            Key::Symbol(_) => None,
56        }
57    }
58}
59
60/// `CanonicalNumericIndexString` restricted to array indices: digits without
61/// a leading zero, below `2^32 - 1`.
62fn canonical_index(units: &[u16]) -> Option<u32> {
63    if units.is_empty() || units.len() > 10 || (units.len() > 1 && units[0] == u16::from(b'0')) {
64        return None;
65    }
66    let mut value: u64 = 0;
67    for &unit in units {
68        let digit = unit.checked_sub(u16::from(b'0')).filter(|&d| d < 10)?;
69        value = value * 10 + u64::from(digit);
70    }
71    (value < u64::from(u32::MAX)).then_some(value as u32)
72}
73
74impl From<SymbolRef> for PropertyKey {
75    fn from(symbol: SymbolRef) -> Self {
76        PropertyKey(Key::Symbol(symbol))
77    }
78}
79
80impl TryFrom<&SymbolRef> for PropertyKey {
81    type Error = RuntimeError;
82
83    fn try_from(symbol: &SymbolRef) -> Result<Self, RuntimeError> {
84        Ok(PropertyKey(Key::Symbol(symbol.clone())))
85    }
86}
87
88/// A descriptor field: absent, or present with a value (`Present(undefined)`
89/// and `Absent` mean different things).
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
91pub enum DescriptorField<T> {
92    #[default]
93    Absent,
94    Present(T),
95}
96
97impl<T> DescriptorField<T> {
98    pub fn try_map<U, E>(
99        &self,
100        map: impl FnOnce(&T) -> Result<U, E>,
101    ) -> Result<DescriptorField<U>, E> {
102        match self {
103            Self::Absent => Ok(DescriptorField::Absent),
104            Self::Present(value) => map(value).map(DescriptorField::Present),
105        }
106    }
107
108    pub const fn is_absent(&self) -> bool {
109        matches!(self, Self::Absent)
110    }
111
112    pub const fn is_present(&self) -> bool {
113        matches!(self, Self::Present(_))
114    }
115
116    pub const fn as_ref(&self) -> DescriptorField<&T> {
117        match self {
118            Self::Absent => DescriptorField::Absent,
119            Self::Present(value) => DescriptorField::Present(value),
120        }
121    }
122
123    pub fn map<U>(self, map: impl FnOnce(T) -> U) -> DescriptorField<U> {
124        match self {
125            Self::Absent => DescriptorField::Absent,
126            Self::Present(value) => DescriptorField::Present(map(value)),
127        }
128    }
129
130    pub fn into_option(self) -> Option<T> {
131        match self {
132            Self::Absent => None,
133            Self::Present(value) => Some(value),
134        }
135    }
136}
137
138impl<T> From<Option<T>> for DescriptorField<T> {
139    fn from(value: Option<T>) -> Self {
140        match value {
141            Some(value) => Self::Present(value),
142            None => Self::Absent,
143        }
144    }
145}
146
147/// A present `get` or `set` field: a function or `undefined`.
148#[derive(Clone, Debug, PartialEq)]
149pub enum AccessorValue {
150    Undefined,
151    Callable(CallableRef),
152}
153
154impl AccessorValue {
155    pub fn try_clone(&self) -> Result<Self, RuntimeError> {
156        Ok(self.clone())
157    }
158}
159
160/// A property descriptor for `[[DefineOwnProperty]]`, possibly partial.
161#[derive(Clone, Debug, Default, PartialEq)]
162pub struct OrdinaryPropertyDescriptor {
163    pub value: DescriptorField<Value>,
164    pub writable: DescriptorField<bool>,
165    pub get: DescriptorField<AccessorValue>,
166    pub set: DescriptorField<AccessorValue>,
167    pub enumerable: DescriptorField<bool>,
168    pub configurable: DescriptorField<bool>,
169}
170
171impl OrdinaryPropertyDescriptor {
172    pub fn new() -> Self {
173        Self::default()
174    }
175
176    pub fn try_clone(&self) -> Result<Self, RuntimeError> {
177        Ok(self.clone())
178    }
179
180    /// A data property with every attribute present.
181    pub fn data(value: Value, writable: bool, enumerable: bool, configurable: bool) -> Self {
182        OrdinaryPropertyDescriptor {
183            value: DescriptorField::Present(value),
184            writable: DescriptorField::Present(writable),
185            enumerable: DescriptorField::Present(enumerable),
186            configurable: DescriptorField::Present(configurable),
187            ..Self::default()
188        }
189    }
190}
191
192/// A complete own property, as `[[GetOwnProperty]]` reports it.
193#[derive(Clone, Debug, PartialEq)]
194pub enum CompleteOrdinaryPropertyDescriptor {
195    Data {
196        value: Value,
197        writable: bool,
198        enumerable: bool,
199        configurable: bool,
200    },
201    Accessor {
202        get: Option<CallableRef>,
203        set: Option<CallableRef>,
204        enumerable: bool,
205        configurable: bool,
206    },
207}
208
209impl CompleteOrdinaryPropertyDescriptor {
210    pub fn try_clone(&self) -> Result<Self, RuntimeError> {
211        Ok(self.clone())
212    }
213
214    pub const fn enumerable(&self) -> bool {
215        match self {
216            Self::Data { enumerable, .. } | Self::Accessor { enumerable, .. } => *enumerable,
217        }
218    }
219
220    pub const fn configurable(&self) -> bool {
221        match self {
222            Self::Data { configurable, .. } | Self::Accessor { configurable, .. } => *configurable,
223        }
224    }
225}
226
227/// The well-known symbols.
228#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
229pub enum WellKnownSymbol {
230    ToPrimitive,
231    Iterator,
232    Match,
233    MatchAll,
234    Replace,
235    Search,
236    Split,
237    ToStringTag,
238    IsConcatSpreadable,
239    HasInstance,
240    Species,
241    Unscopables,
242    AsyncIterator,
243}
244
245impl WellKnownSymbol {
246    pub const ALL: [Self; 13] = [
247        Self::ToPrimitive,
248        Self::Iterator,
249        Self::Match,
250        Self::MatchAll,
251        Self::Replace,
252        Self::Search,
253        Self::Split,
254        Self::ToStringTag,
255        Self::IsConcatSpreadable,
256        Self::HasInstance,
257        Self::Species,
258        Self::Unscopables,
259        Self::AsyncIterator,
260    ];
261
262    pub const fn description(self) -> &'static str {
263        match self {
264            Self::ToPrimitive => "Symbol.toPrimitive",
265            Self::Iterator => "Symbol.iterator",
266            Self::Match => "Symbol.match",
267            Self::MatchAll => "Symbol.matchAll",
268            Self::Replace => "Symbol.replace",
269            Self::Search => "Symbol.search",
270            Self::Split => "Symbol.split",
271            Self::ToStringTag => "Symbol.toStringTag",
272            Self::IsConcatSpreadable => "Symbol.isConcatSpreadable",
273            Self::HasInstance => "Symbol.hasInstance",
274            Self::Species => "Symbol.species",
275            Self::Unscopables => "Symbol.unscopables",
276            Self::AsyncIterator => "Symbol.asyncIterator",
277        }
278    }
279
280    /// The property name on `Symbol` (`"iterator"`).
281    pub fn name(self) -> &'static str {
282        &self.description()["Symbol.".len()..]
283    }
284}