use super::{
JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey,
shape::{
ChangeTransitionAction, RootShape, Shape, UniqueShape,
property_table::PropertyTableInner,
shared_shape::TransitionKey,
slot::{Slot, SlotAttributes},
},
};
use crate::value::JsVariant;
use crate::{JsValue, property::PropertyDescriptorBuilder};
use boa_gc::{Finalize, Trace, custom_trace};
use indexmap::IndexMap;
use rustc_hash::{FxHashMap, FxHasher};
use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator};
use thin_vec::ThinVec;
#[derive(Debug, Finalize)]
#[allow(unused)] 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);
}
});
}
#[derive(Debug, Trace, Finalize)]
pub enum IndexedProperties {
DenseI32(ThinVec<i32>),
DenseF64(ThinVec<f64>),
DenseElement(ThinVec<JsValue>),
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)
}
fn get(&self, key: u32) -> Option<PropertyDescriptor> {
let value = match self {
Self::DenseI32(vec) => vec.get(key as usize).copied()?.into(),
Self::DenseF64(vec) => vec.get(key as usize).copied()?.into(),
Self::DenseElement(vec) => vec.get(key as usize)?.clone(),
Self::Sparse(map) => return map.get(&key).cloned(),
};
Some(
PropertyDescriptorBuilder::new()
.writable(true)
.enumerable(true)
.configurable(true)
.value(value)
.build(),
)
}
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
}
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 {
Self::DenseI32(vec) if key <= vec.len() as u32 => {
let len = vec.len() as u32;
let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits();
let value = match value.variant() {
JsVariant::Integer32(n) => n,
JsVariant::Float64(n) if is_rational_integer(n) => n as i32,
JsVariant::Float64(value) => {
let mut vec = vec.iter().copied().map(f64::from).collect::<ThinVec<_>>();
if key == len {
vec.push(value);
*self = Self::DenseF64(vec);
return false;
}
vec[key as usize] = value;
*self = Self::DenseF64(vec);
return true;
}
_ => {
let mut vec = vec
.iter()
.copied()
.map(JsValue::from)
.collect::<ThinVec<JsValue>>();
if key == len {
vec.push(value);
*self = Self::DenseElement(vec);
return false;
}
vec[key as usize] = value;
*self = Self::DenseElement(vec);
return true;
}
};
if key == len {
vec.push(value);
return false;
}
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 key == len {
vec.push(value);
return false;
}
vec[key as usize] = value;
return true;
}
let mut vec = vec
.iter()
.copied()
.map(JsValue::from)
.collect::<ThinVec<JsValue>>();
if key == len {
vec.push(value);
*self = Self::DenseElement(vec);
return false;
}
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 key == len {
vec.push(value);
return false;
}
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
}
fn remove(&mut self, key: u32) -> bool {
match self {
Self::DenseI32(vec) if (key + 1) == vec.len() as u32 => {
vec.pop();
true
}
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,
Self::Sparse(map) => map.remove(&key).is_some(),
_ => self.convert_to_sparse_and_remove(key),
}
}
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()
}
}
#[derive(Default, Debug, Trace, Finalize)]
pub struct PropertyMap {
pub(crate) indexed_properties: IndexedProperties,
pub(crate) shape: Shape,
pub(crate) storage: ObjectStorage,
}
impl PropertyMap {
#[must_use]
#[inline]
pub fn new(shape: Shape, indexed_properties: IndexedProperties) -> Self {
Self {
indexed_properties,
shape,
storage: Vec::default(),
}
}
#[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(),
}
}
#[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(),
}
}
#[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
}
#[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;
out_slot.attributes = (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS)
| slot.attributes
| SlotAttributes::FOUND;
return Some(self.get_storage(slot));
}
None
}
#[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()
}
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)
}
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 => {
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);
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
}
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) {
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
}
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;
};
let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits();
let value = match value.variant() {
JsVariant::Integer32(n) => n,
JsVariant::Float64(n) if is_rational_integer(n) => n as i32,
JsVariant::Float64(value) => {
let mut properties = properties
.iter()
.copied()
.map(f64::from)
.collect::<ThinVec<_>>();
properties[index] = value;
self.indexed_properties = IndexedProperties::DenseF64(properties);
return true;
}
_ => {
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,
}
}
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,
}
}
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
}
}
#[inline]
#[must_use]
pub fn index_properties(&self) -> IndexProperties<'_> {
self.indexed_properties.iter()
}
#[inline]
#[must_use]
pub fn index_property_keys(&self) -> IndexPropertyKeys<'_> {
self.indexed_properties.keys()
}
#[inline]
#[must_use]
pub fn index_property_values(&self) -> IndexPropertyValues<'_> {
self.indexed_properties.values()
}
#[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
}
}
#[derive(Debug, Clone)]
pub enum IndexProperties<'a> {
DenseI32(std::iter::Enumerate<std::slice::Iter<'a, i32>>),
DenseF64(std::iter::Enumerate<std::slice::Iter<'a, f64>>),
DenseElement(std::iter::Enumerate<std::slice::Iter<'a, JsValue>>),
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<'_> {}
#[derive(Debug, Clone)]
pub enum IndexPropertyKeys<'a> {
Dense(std::ops::Range<u32>),
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<'_> {}
#[derive(Debug, Clone)]
#[allow(variant_size_differences)]
pub enum IndexPropertyValues<'a> {
DenseI32(std::slice::Iter<'a, i32>),
DenseF64(std::slice::Iter<'a, f64>),
DenseElement(std::slice::Iter<'a, JsValue>),
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(),
}
}
}