mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
949 lines
34 KiB
949 lines
34 KiB
use super::{ |
|
shape::{ |
|
property_table::PropertyTableInner, |
|
shared_shape::TransitionKey, |
|
slot::{Slot, SlotAttributes}, |
|
ChangeTransitionAction, RootShape, Shape, UniqueShape, |
|
}, |
|
JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey, |
|
}; |
|
use crate::{property::PropertyDescriptorBuilder, JsString, JsSymbol, JsValue}; |
|
use boa_gc::{custom_trace, Finalize, Trace}; |
|
use indexmap::IndexMap; |
|
use rustc_hash::{FxHashMap, FxHasher}; |
|
use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator}; |
|
use thin_vec::ThinVec; |
|
|
|
/// Wrapper around `indexmap::IndexMap` for usage in `PropertyMap`. |
|
#[derive(Debug, Finalize)] |
|
struct OrderedHashMap<K: Trace>(IndexMap<K, PropertyDescriptor, BuildHasherDefault<FxHasher>>); |
|
|
|
impl<K: Trace> Default for OrderedHashMap<K> { |
|
fn default() -> Self { |
|
Self(IndexMap::with_hasher(BuildHasherDefault::default())) |
|
} |
|
} |
|
|
|
unsafe impl<K: Trace> Trace for OrderedHashMap<K> { |
|
custom_trace!(this, mark, { |
|
for (k, v) in &this.0 { |
|
mark(k); |
|
mark(v); |
|
} |
|
}); |
|
} |
|
|
|
/// This represents all the indexed properties. |
|
/// |
|
/// The index properties can be stored in two storage methods: |
|
/// |
|
/// ## Dense Storage |
|
/// |
|
/// Dense storage holds a contiguous array of properties where the index in the array is the key of the property. |
|
/// These are known to be data descriptors with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`. |
|
/// |
|
/// Since we know the properties of the property descriptors (and they are all the same) we can omit it and just store only |
|
/// the value field and construct the data property descriptor on demand. |
|
/// |
|
/// ## Sparse Storage |
|
/// |
|
/// This storage is used as a backup if the element keys are not continuous or the property descriptors |
|
/// are not data descriptors with with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`. |
|
/// |
|
/// This method uses more space, since we also have to store the property descriptors, not just the value. |
|
/// It is also slower because we need to do a hash lookup. |
|
#[derive(Debug, Trace, Finalize)] |
|
pub enum IndexedProperties { |
|
/// Dense [`i32`] storage. |
|
DenseI32(ThinVec<i32>), |
|
|
|
/// Dense [`f64`] storage. |
|
DenseF64(ThinVec<f64>), |
|
|
|
/// Dense [`JsValue`] storage. |
|
DenseElement(ThinVec<JsValue>), |
|
|
|
/// Sparse [`JsValue`] storage. |
|
Sparse(Box<FxHashMap<u32, PropertyDescriptor>>), |
|
} |
|
|
|
impl Default for IndexedProperties { |
|
#[inline] |
|
fn default() -> Self { |
|
Self::DenseI32(ThinVec::new()) |
|
} |
|
} |
|
|
|
impl IndexedProperties { |
|
pub(crate) fn from_dense_js_value(elements: ThinVec<JsValue>) -> Self { |
|
if elements.is_empty() { |
|
return Self::default(); |
|
} |
|
Self::DenseElement(elements) |
|
} |
|
|
|
/// Get a property descriptor if it exists. |
|
fn get(&self, key: u32) -> Option<PropertyDescriptor> { |
|
let value = match self { |
|
Self::DenseI32(ref vec) => vec.get(key as usize).copied()?.into(), |
|
Self::DenseF64(ref vec) => vec.get(key as usize).copied()?.into(), |
|
Self::DenseElement(ref vec) => vec.get(key as usize)?.clone(), |
|
Self::Sparse(ref map) => return map.get(&key).cloned(), |
|
}; |
|
|
|
Some( |
|
PropertyDescriptorBuilder::new() |
|
.writable(true) |
|
.enumerable(true) |
|
.configurable(true) |
|
.value(value) |
|
.build(), |
|
) |
|
} |
|
|
|
/// Helper function for converting from a dense storage type to sparse storage type. |
|
fn convert_dense_to_sparse<T>(vec: &mut ThinVec<T>) -> FxHashMap<u32, PropertyDescriptor> |
|
where |
|
T: Into<JsValue>, |
|
{ |
|
let data = std::mem::take(vec); |
|
|
|
data.into_iter() |
|
.enumerate() |
|
.map(|(index, value)| { |
|
( |
|
index as u32, |
|
PropertyDescriptorBuilder::new() |
|
.writable(true) |
|
.enumerable(true) |
|
.configurable(true) |
|
.value(value.into()) |
|
.build(), |
|
) |
|
}) |
|
.collect() |
|
} |
|
|
|
fn convert_to_sparse_and_insert(&mut self, key: u32, property: PropertyDescriptor) -> bool { |
|
let mut map = match self { |
|
Self::DenseI32(vec) => Self::convert_dense_to_sparse(vec), |
|
Self::DenseF64(vec) => Self::convert_dense_to_sparse(vec), |
|
Self::DenseElement(vec) => Self::convert_dense_to_sparse(vec), |
|
Self::Sparse(map) => { |
|
return map.insert(key, property).is_some(); |
|
} |
|
}; |
|
|
|
map.insert(key, property); |
|
*self = Self::Sparse(Box::new(map)); |
|
false |
|
} |
|
|
|
/// Inserts a property descriptor with the specified key. |
|
fn insert(&mut self, key: u32, property: PropertyDescriptor) -> bool { |
|
if !property.writable().unwrap_or(false) |
|
|| !property.enumerable().unwrap_or(false) |
|
|| !property.configurable().unwrap_or(false) |
|
{ |
|
return self.convert_to_sparse_and_insert(key, property); |
|
} |
|
let Some(value) = property.value().cloned() else { |
|
return self.convert_to_sparse_and_insert(key, property); |
|
}; |
|
|
|
match self { |
|
// Fast Path: continues array access. |
|
Self::DenseI32(vec) if key <= vec.len() as u32 => { |
|
let len = vec.len() as u32; |
|
|
|
// If it can fit in a i32 and the truncated version is |
|
// equal to the original then it is an integer. |
|
let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits(); |
|
|
|
let value = match value { |
|
JsValue::Integer(n) => n, |
|
JsValue::Rational(n) if is_rational_integer(n) => n as i32, |
|
JsValue::Rational(value) => { |
|
let mut vec = vec.iter().copied().map(f64::from).collect::<ThinVec<_>>(); |
|
|
|
// If the key is pointing one past the last element, we push it! |
|
// |
|
// Since the previous key is the current key - 1. Meaning that the elements are continuos. |
|
if key == len { |
|
vec.push(value); |
|
*self = Self::DenseF64(vec); |
|
return false; |
|
} |
|
|
|
// If it the key points in at a already taken index, set it. |
|
vec[key as usize] = value; |
|
*self = Self::DenseF64(vec); |
|
return true; |
|
} |
|
value => { |
|
let mut vec = vec |
|
.iter() |
|
.copied() |
|
.map(JsValue::from) |
|
.collect::<ThinVec<JsValue>>(); |
|
|
|
// If the key is pointing one past the last element, we push it! |
|
// |
|
// Since the previous key is the current key - 1. Meaning that the elements are continuos. |
|
if key == len { |
|
vec.push(value); |
|
*self = Self::DenseElement(vec); |
|
return false; |
|
} |
|
|
|
// If it the key points in at a already taken index, set it. |
|
vec[key as usize] = value; |
|
*self = Self::DenseElement(vec); |
|
return true; |
|
} |
|
}; |
|
|
|
// If the key is pointing one past the last element, we push it! |
|
// |
|
// Since the previous key is the current key - 1. Meaning that the elements are continuos. |
|
if key == len { |
|
vec.push(value); |
|
return false; |
|
} |
|
|
|
// If it the key points in at a already taken index, swap and return it. |
|
vec[key as usize] = value; |
|
true |
|
} |
|
Self::DenseF64(vec) if key <= vec.len() as u32 => { |
|
let len = vec.len() as u32; |
|
|
|
if let Some(value) = value.as_number() { |
|
// If the key is pointing one past the last element, we push it! |
|
// |
|
// Since the previous key is the current key - 1. Meaning that the elements are continuos. |
|
if key == len { |
|
vec.push(value); |
|
return false; |
|
} |
|
|
|
// If it the key points in at a already taken index, swap and return it. |
|
vec[key as usize] = value; |
|
return true; |
|
} |
|
|
|
let mut vec = vec |
|
.iter() |
|
.copied() |
|
.map(JsValue::from) |
|
.collect::<ThinVec<JsValue>>(); |
|
|
|
// If the key is pointing one past the last element, we push it! |
|
// |
|
// Since the previous key is the current key - 1. Meaning that the elements are continuos. |
|
if key == len { |
|
vec.push(value); |
|
*self = Self::DenseElement(vec); |
|
return false; |
|
} |
|
|
|
// If it the key points in at a already taken index, set it. |
|
vec[key as usize] = value; |
|
*self = Self::DenseElement(vec); |
|
true |
|
} |
|
Self::DenseElement(vec) if key <= vec.len() as u32 => { |
|
let len = vec.len() as u32; |
|
|
|
// If the key is pointing one past the last element, we push it! |
|
// |
|
// Since the previous key is the current key - 1. Meaning that the elements are continuos. |
|
if key == len { |
|
vec.push(value); |
|
return false; |
|
} |
|
|
|
// If it the key points in at a already taken index, set it. |
|
vec[key as usize] = value; |
|
true |
|
} |
|
Self::Sparse(map) => map.insert(key, property).is_some(), |
|
_ => self.convert_to_sparse_and_insert(key, property), |
|
} |
|
} |
|
|
|
fn convert_to_sparse_and_remove(&mut self, key: u32) -> bool { |
|
let mut map = match self { |
|
IndexedProperties::DenseI32(vec) => Self::convert_dense_to_sparse(vec), |
|
IndexedProperties::DenseF64(vec) => Self::convert_dense_to_sparse(vec), |
|
IndexedProperties::DenseElement(vec) => Self::convert_dense_to_sparse(vec), |
|
IndexedProperties::Sparse(map) => return map.remove(&key).is_some(), |
|
}; |
|
|
|
let removed = map.remove(&key).is_some(); |
|
*self = Self::Sparse(Box::new(map)); |
|
removed |
|
} |
|
|
|
/// Removes a property descriptor with the specified key. |
|
fn remove(&mut self, key: u32) -> bool { |
|
match self { |
|
// Fast Paths: contiguous storage. |
|
// |
|
// If the key is pointing at the last element, then we pop it. |
|
Self::DenseI32(vec) if (key + 1) == vec.len() as u32 => { |
|
vec.pop(); |
|
true |
|
} |
|
// If the key is out of range then don't do anything. |
|
Self::DenseI32(vec) if key >= vec.len() as u32 => false, |
|
Self::DenseF64(vec) if (key + 1) == vec.len() as u32 => { |
|
vec.pop(); |
|
true |
|
} |
|
Self::DenseF64(vec) if key >= vec.len() as u32 => false, |
|
Self::DenseElement(vec) if (key + 1) == vec.len() as u32 => { |
|
vec.pop(); |
|
true |
|
} |
|
Self::DenseElement(vec) if key >= vec.len() as u32 => false, |
|
// Slow Paths: non-contiguous storage. |
|
Self::Sparse(map) => map.remove(&key).is_some(), |
|
_ => self.convert_to_sparse_and_remove(key), |
|
} |
|
} |
|
|
|
/// Check if we contain the key to a property descriptor. |
|
fn contains_key(&self, key: u32) -> bool { |
|
match self { |
|
Self::DenseI32(vec) => (0..vec.len() as u32).contains(&key), |
|
Self::DenseF64(vec) => (0..vec.len() as u32).contains(&key), |
|
Self::DenseElement(vec) => (0..vec.len() as u32).contains(&key), |
|
Self::Sparse(map) => map.contains_key(&key), |
|
} |
|
} |
|
|
|
fn iter(&self) -> IndexProperties<'_> { |
|
match self { |
|
Self::DenseI32(vec) => IndexProperties::DenseI32(vec.iter().enumerate()), |
|
Self::DenseF64(vec) => IndexProperties::DenseF64(vec.iter().enumerate()), |
|
Self::DenseElement(vec) => IndexProperties::DenseElement(vec.iter().enumerate()), |
|
Self::Sparse(map) => IndexProperties::Sparse(map.iter()), |
|
} |
|
} |
|
|
|
fn keys(&self) -> IndexPropertyKeys<'_> { |
|
match self { |
|
Self::DenseI32(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32), |
|
Self::DenseF64(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32), |
|
Self::DenseElement(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32), |
|
Self::Sparse(map) => IndexPropertyKeys::Sparse(map.keys()), |
|
} |
|
} |
|
|
|
fn values(&self) -> IndexPropertyValues<'_> { |
|
match self { |
|
Self::DenseI32(vec) => IndexPropertyValues::DenseI32(vec.iter()), |
|
Self::DenseF64(vec) => IndexPropertyValues::DenseF64(vec.iter()), |
|
Self::DenseElement(vec) => IndexPropertyValues::DenseElement(vec.iter()), |
|
Self::Sparse(map) => IndexPropertyValues::Sparse(map.values()), |
|
} |
|
} |
|
} |
|
|
|
impl<'a> IntoIterator for &'a IndexedProperties { |
|
type IntoIter = IndexProperties<'a>; |
|
type Item = (u32, PropertyDescriptor); |
|
fn into_iter(self) -> Self::IntoIter { |
|
self.iter() |
|
} |
|
} |
|
|
|
/// A [`PropertyMap`] contains all the properties of an object. |
|
/// |
|
/// The property values are stored in different data structures based on keys. |
|
#[derive(Default, Debug, Trace, Finalize)] |
|
pub struct PropertyMap { |
|
/// Properties stored with integers as keys. |
|
pub(crate) indexed_properties: IndexedProperties, |
|
|
|
pub(crate) shape: Shape, |
|
pub(crate) storage: ObjectStorage, |
|
} |
|
|
|
impl PropertyMap { |
|
/// Create a new [`PropertyMap`]. |
|
#[must_use] |
|
#[inline] |
|
pub fn new(shape: Shape, indexed_properties: IndexedProperties) -> Self { |
|
Self { |
|
indexed_properties, |
|
shape, |
|
storage: Vec::default(), |
|
} |
|
} |
|
|
|
/// Construct a [`PropertyMap`] from with the given prototype with an unique [`Shape`]. |
|
#[must_use] |
|
#[inline] |
|
pub fn from_prototype_unique_shape(prototype: JsPrototype) -> Self { |
|
Self { |
|
indexed_properties: IndexedProperties::default(), |
|
shape: UniqueShape::new(prototype, PropertyTableInner::default()).into(), |
|
storage: Vec::default(), |
|
} |
|
} |
|
|
|
/// Construct a [`PropertyMap`] from with the given prototype with a shared shape [`Shape`]. |
|
#[must_use] |
|
#[inline] |
|
pub fn from_prototype_with_shared_shape( |
|
root_shape: &RootShape, |
|
prototype: JsPrototype, |
|
) -> Self { |
|
let shape = root_shape.shape().change_prototype_transition(prototype); |
|
Self { |
|
indexed_properties: IndexedProperties::default(), |
|
shape: shape.into(), |
|
storage: Vec::default(), |
|
} |
|
} |
|
|
|
/// Get the property with the given key from the [`PropertyMap`]. |
|
#[must_use] |
|
pub fn get(&self, key: &PropertyKey) -> Option<PropertyDescriptor> { |
|
if let PropertyKey::Index(index) = key { |
|
return self.indexed_properties.get(index.get()); |
|
} |
|
if let Some(slot) = self.shape.lookup(key) { |
|
return Some(self.get_storage(slot)); |
|
} |
|
|
|
None |
|
} |
|
|
|
/// Get the property with the given key from the [`PropertyMap`]. |
|
#[must_use] |
|
pub(crate) fn get_with_slot( |
|
&self, |
|
key: &PropertyKey, |
|
out_slot: &mut Slot, |
|
) -> Option<PropertyDescriptor> { |
|
if let PropertyKey::Index(index) = key { |
|
return self.indexed_properties.get(index.get()); |
|
} |
|
if let Some(slot) = self.shape.lookup(key) { |
|
out_slot.index = slot.index; |
|
|
|
// Remove all descriptor attributes, but keep inline caching bits. |
|
out_slot.attributes = (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) |
|
| slot.attributes |
|
| SlotAttributes::FOUND; |
|
return Some(self.get_storage(slot)); |
|
} |
|
|
|
None |
|
} |
|
|
|
/// Get the property with the given key from the [`PropertyMap`]. |
|
#[must_use] |
|
pub(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor { |
|
let index = index as usize; |
|
let mut builder = PropertyDescriptor::builder() |
|
.configurable(attributes.contains(SlotAttributes::CONFIGURABLE)) |
|
.enumerable(attributes.contains(SlotAttributes::ENUMERABLE)); |
|
if attributes.is_accessor_descriptor() { |
|
if attributes.has_get() { |
|
builder = builder.get(self.storage[index].clone()); |
|
} |
|
if attributes.has_set() { |
|
builder = builder.set(self.storage[index + 1].clone()); |
|
} |
|
} else { |
|
builder = builder.writable(attributes.contains(SlotAttributes::WRITABLE)); |
|
builder = builder.value(self.storage[index].clone()); |
|
} |
|
builder.build() |
|
} |
|
|
|
/// Insert the given property descriptor with the given key [`PropertyMap`]. |
|
pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool { |
|
let mut dummy_slot = Slot::new(); |
|
self.insert_with_slot(key, property, &mut dummy_slot) |
|
} |
|
|
|
/// Insert the given property descriptor with the given key [`PropertyMap`]. |
|
pub(crate) fn insert_with_slot( |
|
&mut self, |
|
key: &PropertyKey, |
|
property: PropertyDescriptor, |
|
out_slot: &mut Slot, |
|
) -> bool { |
|
if let PropertyKey::Index(index) = key { |
|
return self.indexed_properties.insert(index.get(), property); |
|
} |
|
|
|
let attributes = property.to_slot_attributes(); |
|
|
|
if let Some(slot) = self.shape.lookup(key) { |
|
let index = slot.index as usize; |
|
|
|
if slot.attributes != attributes { |
|
let key = TransitionKey { |
|
property_key: key.clone(), |
|
attributes, |
|
}; |
|
let transition = self.shape.change_attributes_transition(key); |
|
self.shape = transition.shape; |
|
match transition.action { |
|
ChangeTransitionAction::Nothing => {} |
|
ChangeTransitionAction::Remove => { |
|
self.storage.remove(slot.index as usize + 1); |
|
} |
|
ChangeTransitionAction::Insert => { |
|
// insert after index which is (index + 1). |
|
self.storage.insert(index, JsValue::undefined()); |
|
} |
|
} |
|
} |
|
|
|
if attributes.is_accessor_descriptor() { |
|
if attributes.has_get() { |
|
self.storage[index] = property |
|
.get() |
|
.cloned() |
|
.map(JsValue::new) |
|
.unwrap_or_default(); |
|
} |
|
if attributes.has_set() { |
|
self.storage[index + 1] = property |
|
.set() |
|
.cloned() |
|
.map(JsValue::new) |
|
.unwrap_or_default(); |
|
} |
|
} else { |
|
self.storage[index] = property.expect_value().clone(); |
|
} |
|
out_slot.index = slot.index; |
|
out_slot.attributes = |
|
(out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes; |
|
return true; |
|
} |
|
|
|
let transition_key = TransitionKey { |
|
property_key: key.clone(), |
|
attributes, |
|
}; |
|
self.shape = self.shape.insert_property_transition(transition_key); |
|
|
|
// Make Sure that if we are inserting, it has the correct slot index. |
|
debug_assert_eq!( |
|
self.shape.lookup(key), |
|
Some(Slot { |
|
index: self.storage.len() as u32, |
|
attributes |
|
}) |
|
); |
|
|
|
out_slot.index = self.storage.len() as u32; |
|
out_slot.attributes = |
|
(out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes; |
|
|
|
if attributes.is_accessor_descriptor() { |
|
self.storage.push( |
|
property |
|
.get() |
|
.cloned() |
|
.map(JsValue::new) |
|
.unwrap_or_default(), |
|
); |
|
self.storage.push( |
|
property |
|
.set() |
|
.cloned() |
|
.map(JsValue::new) |
|
.unwrap_or_default(), |
|
); |
|
} else { |
|
self.storage |
|
.push(property.value().cloned().unwrap_or_default()); |
|
} |
|
|
|
false |
|
} |
|
|
|
/// Remove the property with the given key from the [`PropertyMap`]. |
|
pub fn remove(&mut self, key: &PropertyKey) -> bool { |
|
if let PropertyKey::Index(index) = key { |
|
return self.indexed_properties.remove(index.get()); |
|
} |
|
if let Some(slot) = self.shape.lookup(key) { |
|
// shift all elements when removing. |
|
if slot.attributes.is_accessor_descriptor() { |
|
self.storage.remove(slot.index as usize + 1); |
|
} |
|
self.storage.remove(slot.index as usize); |
|
|
|
self.shape = self.shape.remove_property_transition(key); |
|
return true; |
|
} |
|
|
|
false |
|
} |
|
|
|
/// Overrides all the indexed properties, setting it to dense storage. |
|
pub(crate) fn override_indexed_properties(&mut self, properties: ThinVec<JsValue>) { |
|
self.indexed_properties = IndexedProperties::DenseElement(properties); |
|
} |
|
|
|
pub(crate) fn get_dense_property(&self, index: u32) -> Option<JsValue> { |
|
let index = index as usize; |
|
match &self.indexed_properties { |
|
IndexedProperties::DenseI32(properties) => { |
|
properties.get(index).copied().map(JsValue::from) |
|
} |
|
IndexedProperties::DenseF64(properties) => { |
|
properties.get(index).copied().map(JsValue::from) |
|
} |
|
IndexedProperties::DenseElement(properties) => properties.get(index).cloned(), |
|
IndexedProperties::Sparse(_) => None, |
|
} |
|
} |
|
|
|
pub(crate) fn set_dense_property(&mut self, index: u32, value: &JsValue) -> bool { |
|
let index = index as usize; |
|
|
|
match &mut self.indexed_properties { |
|
IndexedProperties::DenseI32(properties) => { |
|
let Some(element) = properties.get_mut(index) else { |
|
return false; |
|
}; |
|
|
|
// If it can fit in a i32 and the truncated version is |
|
// equal to the original then it is an integer. |
|
let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits(); |
|
|
|
let value = match value { |
|
JsValue::Integer(n) => *n, |
|
JsValue::Rational(n) if is_rational_integer(*n) => *n as i32, |
|
JsValue::Rational(value) => { |
|
let mut properties = properties |
|
.iter() |
|
.copied() |
|
.map(f64::from) |
|
.collect::<ThinVec<_>>(); |
|
properties[index] = *value; |
|
self.indexed_properties = IndexedProperties::DenseF64(properties); |
|
|
|
return true; |
|
} |
|
value => { |
|
let mut properties = properties |
|
.iter() |
|
.copied() |
|
.map(JsValue::from) |
|
.collect::<ThinVec<_>>(); |
|
properties[index] = value.clone(); |
|
self.indexed_properties = IndexedProperties::DenseElement(properties); |
|
|
|
return true; |
|
} |
|
}; |
|
|
|
*element = value; |
|
true |
|
} |
|
IndexedProperties::DenseF64(properties) => { |
|
let Some(element) = properties.get_mut(index) else { |
|
return false; |
|
}; |
|
|
|
let Some(value) = value.as_number() else { |
|
let mut properties = properties |
|
.iter() |
|
.copied() |
|
.map(JsValue::from) |
|
.collect::<ThinVec<_>>(); |
|
properties[index] = value.clone(); |
|
self.indexed_properties = IndexedProperties::DenseElement(properties); |
|
return true; |
|
}; |
|
|
|
*element = value; |
|
true |
|
} |
|
IndexedProperties::DenseElement(properties) => { |
|
let Some(element) = properties.get_mut(index) else { |
|
return false; |
|
}; |
|
*element = value.clone(); |
|
true |
|
} |
|
IndexedProperties::Sparse(_) => false, |
|
} |
|
} |
|
|
|
/// Returns the vec of dense indexed properties if they exist. |
|
pub(crate) fn to_dense_indexed_properties(&self) -> Option<ThinVec<JsValue>> { |
|
match &self.indexed_properties { |
|
IndexedProperties::DenseI32(properties) => { |
|
Some(properties.iter().copied().map(JsValue::from).collect()) |
|
} |
|
IndexedProperties::DenseF64(properties) => { |
|
Some(properties.iter().copied().map(JsValue::from).collect()) |
|
} |
|
IndexedProperties::DenseElement(properties) => Some(properties.clone()), |
|
IndexedProperties::Sparse(_) => None, |
|
} |
|
} |
|
|
|
/// Returns the vec of dense indexed properties if they exist. |
|
pub(crate) fn dense_indexed_properties_mut(&mut self) -> Option<&mut ThinVec<JsValue>> { |
|
if let IndexedProperties::DenseElement(properties) = &mut self.indexed_properties { |
|
Some(properties) |
|
} else { |
|
None |
|
} |
|
} |
|
|
|
/// An iterator visiting all indexed key-value pairs in arbitrary order. The iterator element type is `(&'a u32, &'a Property)`. |
|
/// |
|
/// This iterator does not recurse down the prototype chain. |
|
#[inline] |
|
#[must_use] |
|
pub fn index_properties(&self) -> IndexProperties<'_> { |
|
self.indexed_properties.iter() |
|
} |
|
|
|
/// An iterator visiting all index keys in arbitrary order. The iterator element type is `&'a u32`. |
|
/// |
|
/// This iterator does not recurse down the prototype chain. |
|
#[inline] |
|
#[must_use] |
|
pub fn index_property_keys(&self) -> IndexPropertyKeys<'_> { |
|
self.indexed_properties.keys() |
|
} |
|
|
|
/// An iterator visiting all index values in arbitrary order. The iterator element type is `&'a Property`. |
|
/// |
|
/// This iterator does not recurse down the prototype chain. |
|
#[inline] |
|
#[must_use] |
|
pub fn index_property_values(&self) -> IndexPropertyValues<'_> { |
|
self.indexed_properties.values() |
|
} |
|
|
|
/// Returns `true` if the given key is contained in the [`PropertyMap`]. |
|
#[inline] |
|
#[must_use] |
|
pub fn contains_key(&self, key: &PropertyKey) -> bool { |
|
if let PropertyKey::Index(index) = key { |
|
return self.indexed_properties.contains_key(index.get()); |
|
} |
|
if self.shape.lookup(key).is_some() { |
|
return true; |
|
} |
|
|
|
false |
|
} |
|
} |
|
|
|
/// An iterator over the property entries of an `Object` |
|
#[derive(Debug, Clone)] |
|
pub struct Iter<'a> { |
|
indexed_properties: IndexProperties<'a>, |
|
string_properties: indexmap::map::Iter<'a, JsString, PropertyDescriptor>, |
|
symbol_properties: indexmap::map::Iter<'a, JsSymbol, PropertyDescriptor>, |
|
} |
|
|
|
impl Iterator for Iter<'_> { |
|
type Item = (PropertyKey, PropertyDescriptor); |
|
fn next(&mut self) -> Option<Self::Item> { |
|
if let Some((key, value)) = self.indexed_properties.next() { |
|
Some((key.into(), value)) |
|
} else if let Some((key, value)) = self.string_properties.next() { |
|
Some((key.clone().into(), value.clone())) |
|
} else { |
|
let (key, value) = self.symbol_properties.next()?; |
|
Some((key.clone().into(), value.clone())) |
|
} |
|
} |
|
} |
|
|
|
impl ExactSizeIterator for Iter<'_> { |
|
#[inline] |
|
fn len(&self) -> usize { |
|
self.indexed_properties.len() + self.string_properties.len() + self.symbol_properties.len() |
|
} |
|
} |
|
|
|
/// An iterator over the indexed property entries of an `Object`. |
|
#[derive(Debug, Clone)] |
|
pub enum IndexProperties<'a> { |
|
/// An iterator over dense i32, Vec backed indexed property entries of an `Object`. |
|
DenseI32(std::iter::Enumerate<std::slice::Iter<'a, i32>>), |
|
|
|
/// An iterator over dense f64, Vec backed indexed property entries of an `Object`. |
|
DenseF64(std::iter::Enumerate<std::slice::Iter<'a, f64>>), |
|
|
|
/// An iterator over dense, Vec backed indexed property entries of an `Object`. |
|
DenseElement(std::iter::Enumerate<std::slice::Iter<'a, JsValue>>), |
|
|
|
/// An iterator over sparse, HashMap backed indexed property entries of an `Object`. |
|
Sparse(hash_map::Iter<'a, u32, PropertyDescriptor>), |
|
} |
|
|
|
impl Iterator for IndexProperties<'_> { |
|
type Item = (u32, PropertyDescriptor); |
|
|
|
fn next(&mut self) -> Option<Self::Item> { |
|
let (index, value) = match self { |
|
Self::DenseI32(vec) => vec |
|
.next() |
|
.map(|(index, value)| (index, JsValue::from(*value)))?, |
|
Self::DenseF64(vec) => vec |
|
.next() |
|
.map(|(index, value)| (index, JsValue::from(*value)))?, |
|
Self::DenseElement(vec) => vec.next().map(|(index, value)| (index, value.clone()))?, |
|
Self::Sparse(map) => return map.next().map(|(index, value)| (*index, value.clone())), |
|
}; |
|
|
|
Some(( |
|
index as u32, |
|
PropertyDescriptorBuilder::new() |
|
.writable(true) |
|
.configurable(true) |
|
.enumerable(true) |
|
.value(value.clone()) |
|
.build(), |
|
)) |
|
} |
|
|
|
#[inline] |
|
fn size_hint(&self) -> (usize, Option<usize>) { |
|
match self { |
|
Self::DenseI32(vec) => vec.size_hint(), |
|
Self::DenseF64(vec) => vec.size_hint(), |
|
Self::DenseElement(vec) => vec.size_hint(), |
|
Self::Sparse(map) => map.size_hint(), |
|
} |
|
} |
|
} |
|
|
|
impl ExactSizeIterator for IndexProperties<'_> { |
|
#[inline] |
|
fn len(&self) -> usize { |
|
match self { |
|
Self::DenseI32(vec) => vec.len(), |
|
Self::DenseF64(vec) => vec.len(), |
|
Self::DenseElement(vec) => vec.len(), |
|
Self::Sparse(map) => map.len(), |
|
} |
|
} |
|
} |
|
|
|
impl FusedIterator for IndexProperties<'_> {} |
|
|
|
/// An iterator over the index keys (`u32`) of an `Object`. |
|
#[derive(Debug, Clone)] |
|
pub enum IndexPropertyKeys<'a> { |
|
/// An iterator over dense, Vec backed indexed property entries of an `Object`. |
|
Dense(std::ops::Range<u32>), |
|
|
|
/// An iterator over sparse, HashMap backed indexed property entries of an `Object`. |
|
Sparse(hash_map::Keys<'a, u32, PropertyDescriptor>), |
|
} |
|
|
|
impl Iterator for IndexPropertyKeys<'_> { |
|
type Item = u32; |
|
|
|
#[inline] |
|
fn next(&mut self) -> Option<Self::Item> { |
|
match self { |
|
Self::Dense(vec) => vec.next(), |
|
Self::Sparse(map) => map.next().copied(), |
|
} |
|
} |
|
|
|
#[inline] |
|
fn size_hint(&self) -> (usize, Option<usize>) { |
|
match self { |
|
Self::Dense(vec) => vec.size_hint(), |
|
Self::Sparse(map) => map.size_hint(), |
|
} |
|
} |
|
} |
|
|
|
impl ExactSizeIterator for IndexPropertyKeys<'_> { |
|
#[inline] |
|
fn len(&self) -> usize { |
|
match self { |
|
Self::Dense(vec) => vec.len(), |
|
Self::Sparse(map) => map.len(), |
|
} |
|
} |
|
} |
|
|
|
impl FusedIterator for IndexPropertyKeys<'_> {} |
|
|
|
/// An iterator over the index values (`Property`) of an `Object`. |
|
#[derive(Debug, Clone)] |
|
#[allow(variant_size_differences)] |
|
pub enum IndexPropertyValues<'a> { |
|
/// An iterator over dense, Vec backed indexed property entries of an `Object`. |
|
DenseI32(std::slice::Iter<'a, i32>), |
|
|
|
/// An iterator over dense, Vec backed indexed property entries of an `Object`. |
|
DenseF64(std::slice::Iter<'a, f64>), |
|
|
|
/// An iterator over dense, Vec backed indexed property entries of an `Object`. |
|
DenseElement(std::slice::Iter<'a, JsValue>), |
|
|
|
/// An iterator over sparse, HashMap backed indexed property entries of an `Object`. |
|
Sparse(hash_map::Values<'a, u32, PropertyDescriptor>), |
|
} |
|
|
|
impl Iterator for IndexPropertyValues<'_> { |
|
type Item = PropertyDescriptor; |
|
|
|
fn next(&mut self) -> Option<Self::Item> { |
|
let value = match self { |
|
Self::DenseI32(vec) => vec.next().copied()?.into(), |
|
Self::DenseF64(vec) => vec.next().copied()?.into(), |
|
Self::DenseElement(vec) => vec.next().cloned()?, |
|
Self::Sparse(map) => return map.next().cloned(), |
|
}; |
|
|
|
Some( |
|
PropertyDescriptorBuilder::new() |
|
.writable(true) |
|
.configurable(true) |
|
.enumerable(true) |
|
.value(value) |
|
.build(), |
|
) |
|
} |
|
|
|
#[inline] |
|
fn size_hint(&self) -> (usize, Option<usize>) { |
|
match self { |
|
Self::DenseI32(vec) => vec.size_hint(), |
|
Self::DenseF64(vec) => vec.size_hint(), |
|
Self::DenseElement(vec) => vec.size_hint(), |
|
Self::Sparse(map) => map.size_hint(), |
|
} |
|
} |
|
} |
|
|
|
impl ExactSizeIterator for IndexPropertyValues<'_> { |
|
#[inline] |
|
fn len(&self) -> usize { |
|
match self { |
|
Self::DenseI32(vec) => vec.len(), |
|
Self::DenseF64(vec) => vec.len(), |
|
Self::DenseElement(vec) => vec.len(), |
|
Self::Sparse(map) => map.len(), |
|
} |
|
} |
|
}
|
|
|