feat: works

This commit is contained in:
2022-07-17 10:11:20 +08:00
parent 4ba63b4c2e
commit 74a202f1ed
458 changed files with 125067 additions and 8 deletions

View File

@@ -0,0 +1,113 @@
//! This module implements the `Attribute` struct which contains the attibutes for property descriptors.
use bitflags::bitflags;
use boa_gc::{unsafe_empty_trace, Finalize, Trace};
#[cfg(test)]
mod tests;
bitflags! {
/// This struct constains the property flags as described in the ECMAScript specification.
///
/// It contains the following flags:
/// - `[[Writable]]` (`WRITABLE`) - If `false`, attempts by ECMAScript code to change the property's
/// `[[Value]]` attribute using `[[Set]]` will not succeed.
/// - `[[Enumerable]]` (`ENUMERABLE`) - If the property will be enumerated by a for-in enumeration.
/// - `[[Configurable]]` (`CONFIGURABLE`) - If `false`, attempts to delete the property,
/// change the property to be an `accessor property`, or change its attributes (other than `[[Value]]`,
/// or changing `[[Writable]]` to `false`) will fail.
#[derive(Finalize)]
pub struct Attribute: u8 {
/// The `Writable` attribute decides whether the value associated with the property can be changed or not, from its initial value.
const WRITABLE = 0b0000_0001;
/// If the property can be enumerated by a `for-in` loop.
const ENUMERABLE = 0b0000_0010;
/// If the property descriptor can be changed later.
const CONFIGURABLE = 0b0000_0100;
/// The property is not writable.
const READONLY = 0b0000_0000;
/// The property can not be enumerated in a `for-in` loop.
const NON_ENUMERABLE = 0b0000_0000;
/// The property descriptor cannot be changed.
const PERMANENT = 0b0000_0000;
}
}
// We implement `Trace` manualy rather that wih derive, beacuse `rust-gc`,
// derive `Trace` does not allow `Copy` and `Trace` to be both implemented.
//
// SAFETY: The `Attribute` struct only contains an `u8`
// and therefore it should be safe to implement an empty trace.
unsafe impl Trace for Attribute {
unsafe_empty_trace!();
}
impl Attribute {
/// Clear all flags.
#[inline]
pub fn clear(&mut self) {
self.bits = 0;
}
/// Sets the `writable` flag.
#[inline]
pub fn set_writable(&mut self, value: bool) {
if value {
*self |= Self::WRITABLE;
} else {
*self |= *self & !Self::WRITABLE;
}
}
/// Gets the `writable` flag.
#[inline]
pub fn writable(self) -> bool {
self.contains(Self::WRITABLE)
}
/// Sets the `enumerable` flag.
#[inline]
pub fn set_enumerable(&mut self, value: bool) {
if value {
*self |= Self::ENUMERABLE;
} else {
*self |= *self & !Self::ENUMERABLE;
}
}
/// Gets the `enumerable` flag.
#[inline]
pub fn enumerable(self) -> bool {
self.contains(Self::ENUMERABLE)
}
/// Sets the `configurable` flag.
#[inline]
pub fn set_configurable(&mut self, value: bool) {
if value {
*self |= Self::CONFIGURABLE;
} else {
*self |= *self & !Self::CONFIGURABLE;
}
}
/// Gets the `configurable` flag.
#[inline]
pub fn configurable(self) -> bool {
self.contains(Self::CONFIGURABLE)
}
}
impl Default for Attribute {
/// Returns the default flags according to the [ECMAScript specification][spec].
///
/// [spec]: https://tc39.es/ecma262/#table-default-attribute-values
fn default() -> Self {
Self::READONLY | Self::NON_ENUMERABLE | Self::PERMANENT
}
}

View File

@@ -0,0 +1,137 @@
use super::Attribute;
#[test]
fn writable() {
let attribute = Attribute::WRITABLE;
assert!(attribute.writable());
}
#[test]
fn enumerable() {
let attribute = Attribute::ENUMERABLE;
assert!(attribute.enumerable());
}
#[test]
fn configurable() {
let attribute = Attribute::CONFIGURABLE;
assert!(attribute.configurable());
}
#[test]
fn writable_and_enumerable() {
let attribute = Attribute::WRITABLE | Attribute::ENUMERABLE;
assert!(attribute.writable());
assert!(attribute.enumerable());
}
#[test]
fn enumerable_configurable() {
let attribute = Attribute::ENUMERABLE | Attribute::CONFIGURABLE;
assert!(!attribute.writable());
assert!(attribute.enumerable());
assert!(attribute.configurable());
}
#[test]
fn writable_enumerable_configurable() {
let attribute = Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE;
assert!(attribute.writable());
assert!(attribute.enumerable());
assert!(attribute.configurable());
}
#[test]
fn default() {
let attribute = Attribute::default();
assert!(!attribute.writable());
assert!(!attribute.enumerable());
assert!(!attribute.configurable());
}
#[test]
fn clear() {
let mut attribute = Attribute::default();
attribute.clear();
assert!(!attribute.writable());
assert!(!attribute.enumerable());
assert!(!attribute.configurable());
assert!(attribute.is_empty());
}
#[test]
fn set_writable_to_true() {
let mut attribute = Attribute::default();
attribute.set_writable(true);
assert!(attribute.writable());
assert!(!attribute.enumerable());
assert!(!attribute.configurable());
}
#[test]
fn set_writable_to_false() {
let mut attribute = Attribute::default();
attribute.set_writable(false);
assert!(!attribute.writable());
assert!(!attribute.enumerable());
assert!(!attribute.configurable());
}
#[test]
fn set_enumerable_to_true() {
let mut attribute = Attribute::default();
attribute.set_enumerable(true);
assert!(!attribute.writable());
assert!(attribute.enumerable());
assert!(!attribute.configurable());
}
#[test]
fn set_enumerable_to_false() {
let mut attribute = Attribute::default();
attribute.set_enumerable(false);
assert!(!attribute.writable());
assert!(!attribute.enumerable());
assert!(!attribute.configurable());
}
#[test]
fn set_configurable_to_true() {
let mut attribute = Attribute::default();
attribute.set_configurable(true);
assert!(!attribute.writable());
assert!(!attribute.enumerable());
assert!(attribute.configurable());
}
#[test]
fn set_configurable_to_false() {
let mut attribute = Attribute::default();
attribute.set_configurable(false);
assert!(!attribute.writable());
assert!(!attribute.enumerable());
assert!(!attribute.configurable());
}

View File

@@ -0,0 +1,685 @@
//! This module implements the Property Descriptor.
//!
//! The Property Descriptor type is used to explain the manipulation and reification of `Object`
//! property attributes. Values of the Property Descriptor type are Records. Each field's name is
//! an attribute name and its value is a corresponding attribute value as specified in
//! [6.1.7.1][section]. In addition, any field may be present or absent. The schema name used
//! within this specification to tag literal descriptions of Property Descriptor records is
//! `PropertyDescriptor`.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
//! [section]: https://tc39.es/ecma262/#sec-property-attributes
use crate::{JsString, JsSymbol, JsValue};
use boa_gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt;
mod attribute;
pub use attribute::Attribute;
/// This represents a JavaScript Property AKA The Property Descriptor.
///
/// Property descriptors present in objects come in three main flavors:
/// - data descriptors
/// - accessor descriptors
/// - generic descriptor
///
/// A data Property Descriptor is one that includes any fields named either
/// \[\[Value\]\] or \[\[Writable\]\].
///
/// An accessor Property Descriptor is one that includes any fields named either
/// \[\[Get\]\] or \[\[Set\]\].
///
/// A generic Property Descriptor is a Property Descriptor value that is neither
/// a data Property Descriptor nor an accessor Property Descriptor.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
#[derive(Default, Debug, Clone, Trace, Finalize)]
pub struct PropertyDescriptor {
enumerable: Option<bool>,
configurable: Option<bool>,
kind: DescriptorKind,
}
#[derive(Debug, Clone, Trace, Finalize)]
pub enum DescriptorKind {
Data {
value: Option<JsValue>,
writable: Option<bool>,
},
Accessor {
get: Option<JsValue>,
set: Option<JsValue>,
},
Generic,
}
impl Default for DescriptorKind {
fn default() -> Self {
Self::Generic
}
}
impl PropertyDescriptor {
/// An accessor Property Descriptor is one that includes any fields named either `[[Get]]` or `[[Set]]`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor
#[inline]
pub fn is_accessor_descriptor(&self) -> bool {
matches!(self.kind, DescriptorKind::Accessor { .. })
}
/// A data Property Descriptor is one that includes any fields named either `[[Value]]` or `[[Writable]]`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor
#[inline]
pub fn is_data_descriptor(&self) -> bool {
matches!(self.kind, DescriptorKind::Data { .. })
}
/// A generic Property Descriptor is one that is neither a data descriptor nor an accessor descriptor.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor
#[inline]
pub fn is_generic_descriptor(&self) -> bool {
matches!(self.kind, DescriptorKind::Generic)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.is_generic_descriptor() && self.enumerable.is_none() && self.configurable.is_none()
}
#[inline]
pub fn enumerable(&self) -> Option<bool> {
self.enumerable
}
#[inline]
pub fn configurable(&self) -> Option<bool> {
self.configurable
}
#[inline]
pub fn writable(&self) -> Option<bool> {
match self.kind {
DescriptorKind::Data { writable, .. } => writable,
_ => None,
}
}
#[inline]
pub fn value(&self) -> Option<&JsValue> {
match &self.kind {
DescriptorKind::Data { value, .. } => value.as_ref(),
_ => None,
}
}
#[inline]
pub fn get(&self) -> Option<&JsValue> {
match &self.kind {
DescriptorKind::Accessor { get, .. } => get.as_ref(),
_ => None,
}
}
#[inline]
pub fn set(&self) -> Option<&JsValue> {
match &self.kind {
DescriptorKind::Accessor { set, .. } => set.as_ref(),
_ => None,
}
}
#[inline]
pub fn expect_enumerable(&self) -> bool {
if let Some(enumerable) = self.enumerable {
enumerable
} else {
panic!("[[enumerable]] field not in property descriptor")
}
}
#[inline]
pub fn expect_configurable(&self) -> bool {
if let Some(configurable) = self.configurable {
configurable
} else {
panic!("[[configurable]] field not in property descriptor")
}
}
#[inline]
pub fn expect_writable(&self) -> bool {
if let Some(writable) = self.writable() {
writable
} else {
panic!("[[writable]] field not in property descriptor")
}
}
#[inline]
pub fn expect_value(&self) -> &JsValue {
if let Some(value) = self.value() {
value
} else {
panic!("[[value]] field not in property descriptor")
}
}
#[inline]
pub fn expect_get(&self) -> &JsValue {
if let Some(get) = self.get() {
get
} else {
panic!("[[get]] field not in property descriptor")
}
}
#[inline]
pub fn expect_set(&self) -> &JsValue {
if let Some(set) = self.set() {
set
} else {
panic!("[[set]] field not in property descriptor")
}
}
#[inline]
pub fn kind(&self) -> &DescriptorKind {
&self.kind
}
#[inline]
pub fn builder() -> PropertyDescriptorBuilder {
PropertyDescriptorBuilder::new()
}
#[inline]
#[must_use]
pub fn into_accessor_defaulted(mut self) -> Self {
self.kind = DescriptorKind::Accessor {
get: self.get().cloned(),
set: self.set().cloned(),
};
PropertyDescriptorBuilder { inner: self }
.complete_with_defaults()
.build()
}
#[must_use]
pub fn into_data_defaulted(mut self) -> Self {
self.kind = DescriptorKind::Data {
value: self.value().cloned(),
writable: self.writable(),
};
PropertyDescriptorBuilder { inner: self }
.complete_with_defaults()
.build()
}
#[inline]
#[must_use]
pub fn complete_property_descriptor(self) -> Self {
PropertyDescriptorBuilder { inner: self }
.complete_with_defaults()
.build()
}
#[inline]
pub fn fill_with(&mut self, desc: &Self) {
match (&mut self.kind, &desc.kind) {
(
DescriptorKind::Data { value, writable },
DescriptorKind::Data {
value: desc_value,
writable: desc_writable,
},
) => {
if let Some(desc_value) = desc_value {
*value = Some(desc_value.clone());
}
if let Some(desc_writable) = desc_writable {
*writable = Some(*desc_writable);
}
}
(
DescriptorKind::Accessor { get, set },
DescriptorKind::Accessor {
get: desc_get,
set: desc_set,
},
) => {
if let Some(desc_get) = desc_get {
*get = Some(desc_get.clone());
}
if let Some(desc_set) = desc_set {
*set = Some(desc_set.clone());
}
}
(_, DescriptorKind::Generic) => {}
_ => panic!("Tried to fill a descriptor with an incompatible descriptor"),
}
if let Some(enumerable) = desc.enumerable {
self.enumerable = Some(enumerable);
}
if let Some(configurable) = desc.configurable {
self.configurable = Some(configurable);
}
}
}
#[derive(Default, Debug, Clone)]
pub struct PropertyDescriptorBuilder {
inner: PropertyDescriptor,
}
impl PropertyDescriptorBuilder {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn value<V: Into<JsValue>>(mut self, value: V) -> Self {
match self.inner.kind {
DescriptorKind::Data {
value: ref mut v, ..
} => *v = Some(value.into()),
// TODO: maybe panic when trying to convert accessor to data?
_ => {
self.inner.kind = DescriptorKind::Data {
value: Some(value.into()),
writable: None,
}
}
}
self
}
#[must_use]
pub fn writable(mut self, writable: bool) -> Self {
match self.inner.kind {
DescriptorKind::Data {
writable: ref mut w,
..
} => *w = Some(writable),
// TODO: maybe panic when trying to convert accessor to data?
_ => {
self.inner.kind = DescriptorKind::Data {
value: None,
writable: Some(writable),
}
}
}
self
}
#[must_use]
pub fn get<V: Into<JsValue>>(mut self, get: V) -> Self {
match self.inner.kind {
DescriptorKind::Accessor { get: ref mut g, .. } => *g = Some(get.into()),
// TODO: maybe panic when trying to convert data to accessor?
_ => {
self.inner.kind = DescriptorKind::Accessor {
get: Some(get.into()),
set: None,
}
}
}
self
}
#[must_use]
pub fn set<V: Into<JsValue>>(mut self, set: V) -> Self {
match self.inner.kind {
DescriptorKind::Accessor { set: ref mut s, .. } => *s = Some(set.into()),
// TODO: maybe panic when trying to convert data to accessor?
_ => {
self.inner.kind = DescriptorKind::Accessor {
set: Some(set.into()),
get: None,
}
}
}
self
}
#[must_use]
pub fn maybe_enumerable(mut self, enumerable: Option<bool>) -> Self {
if let Some(enumerable) = enumerable {
self = self.enumerable(enumerable);
}
self
}
#[must_use]
pub fn maybe_configurable(mut self, configurable: Option<bool>) -> Self {
if let Some(configurable) = configurable {
self = self.configurable(configurable);
}
self
}
#[must_use]
pub fn maybe_value<V: Into<JsValue>>(mut self, value: Option<V>) -> Self {
if let Some(value) = value {
self = self.value(value);
}
self
}
#[must_use]
pub fn maybe_writable(mut self, writable: Option<bool>) -> Self {
if let Some(writable) = writable {
self = self.writable(writable);
}
self
}
#[must_use]
pub fn maybe_get<V: Into<JsValue>>(mut self, get: Option<V>) -> Self {
if let Some(get) = get {
self = self.get(get);
}
self
}
#[must_use]
pub fn maybe_set<V: Into<JsValue>>(mut self, set: Option<V>) -> Self {
if let Some(set) = set {
self = self.set(set);
}
self
}
#[must_use]
pub fn enumerable(mut self, enumerable: bool) -> Self {
self.inner.enumerable = Some(enumerable);
self
}
#[must_use]
pub fn configurable(mut self, configurable: bool) -> Self {
self.inner.configurable = Some(configurable);
self
}
#[must_use]
pub fn complete_with_defaults(mut self) -> Self {
match self.inner.kind {
DescriptorKind::Generic => {
self.inner.kind = DescriptorKind::Data {
value: Some(JsValue::undefined()),
writable: Some(false),
}
}
DescriptorKind::Data {
ref mut value,
ref mut writable,
} => {
if value.is_none() {
*value = Some(JsValue::undefined());
}
if writable.is_none() {
*writable = Some(false);
}
}
DescriptorKind::Accessor {
ref mut set,
ref mut get,
} => {
if set.is_none() {
*set = Some(JsValue::undefined());
}
if get.is_none() {
*get = Some(JsValue::undefined());
}
}
}
if self.inner.configurable.is_none() {
self.inner.configurable = Some(false);
}
if self.inner.enumerable.is_none() {
self.inner.enumerable = Some(false);
}
self
}
pub fn inner(&self) -> &PropertyDescriptor {
&self.inner
}
pub fn build(self) -> PropertyDescriptor {
self.inner
}
}
impl From<PropertyDescriptorBuilder> for PropertyDescriptor {
fn from(builder: PropertyDescriptorBuilder) -> Self {
builder.build()
}
}
/// This abstracts away the need for `IsPropertyKey` by transforming the `PropertyKey`
/// values into an enum with both valid types: String and Symbol
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ispropertykey
#[derive(Trace, Finalize, PartialEq, Debug, Clone, Eq, Hash)]
pub enum PropertyKey {
String(JsString),
Symbol(JsSymbol),
Index(u32),
}
impl From<JsString> for PropertyKey {
#[inline]
fn from(string: JsString) -> Self {
if let Ok(index) = string.parse() {
Self::Index(index)
} else {
Self::String(string)
}
}
}
impl From<&str> for PropertyKey {
#[inline]
fn from(string: &str) -> Self {
if let Ok(index) = string.parse() {
Self::Index(index)
} else {
Self::String(string.into())
}
}
}
impl From<String> for PropertyKey {
#[inline]
fn from(string: String) -> Self {
if let Ok(index) = string.parse() {
Self::Index(index)
} else {
Self::String(string.into())
}
}
}
impl From<Box<str>> for PropertyKey {
#[inline]
fn from(string: Box<str>) -> Self {
if let Ok(index) = string.parse() {
Self::Index(index)
} else {
Self::String(string.into())
}
}
}
impl From<JsSymbol> for PropertyKey {
#[inline]
fn from(symbol: JsSymbol) -> Self {
Self::Symbol(symbol)
}
}
impl fmt::Display for PropertyKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(ref string) => string.fmt(f),
Self::Symbol(ref symbol) => symbol.fmt(f),
Self::Index(index) => index.fmt(f),
}
}
}
impl From<&PropertyKey> for JsValue {
#[inline]
fn from(property_key: &PropertyKey) -> Self {
match property_key {
PropertyKey::String(ref string) => string.clone().into(),
PropertyKey::Symbol(ref symbol) => symbol.clone().into(),
PropertyKey::Index(index) => {
if let Ok(integer) = i32::try_from(*index) {
Self::new(integer)
} else {
Self::new(*index)
}
}
}
}
}
impl From<PropertyKey> for JsValue {
#[inline]
fn from(property_key: PropertyKey) -> Self {
match property_key {
PropertyKey::String(ref string) => string.clone().into(),
PropertyKey::Symbol(ref symbol) => symbol.clone().into(),
PropertyKey::Index(index) => index.to_string().into(),
}
}
}
impl From<u8> for PropertyKey {
fn from(value: u8) -> Self {
Self::Index(value.into())
}
}
impl From<u16> for PropertyKey {
fn from(value: u16) -> Self {
Self::Index(value.into())
}
}
impl From<u32> for PropertyKey {
fn from(value: u32) -> Self {
Self::Index(value)
}
}
impl From<usize> for PropertyKey {
fn from(value: usize) -> Self {
if let Ok(index) = u32::try_from(value) {
Self::Index(index)
} else {
Self::String(JsString::from(value.to_string()))
}
}
}
impl From<i64> for PropertyKey {
fn from(value: i64) -> Self {
if let Ok(index) = u32::try_from(value) {
Self::Index(index)
} else {
Self::String(JsString::from(value.to_string()))
}
}
}
impl From<u64> for PropertyKey {
fn from(value: u64) -> Self {
if let Ok(index) = u32::try_from(value) {
Self::Index(index)
} else {
Self::String(JsString::from(value.to_string()))
}
}
}
impl From<isize> for PropertyKey {
fn from(value: isize) -> Self {
if let Ok(index) = u32::try_from(value) {
Self::Index(index)
} else {
Self::String(JsString::from(value.to_string()))
}
}
}
impl From<i32> for PropertyKey {
fn from(value: i32) -> Self {
if let Ok(index) = u32::try_from(value) {
Self::Index(index)
} else {
Self::String(JsString::from(value.to_string()))
}
}
}
impl From<f64> for PropertyKey {
fn from(value: f64) -> Self {
use num_traits::cast::FromPrimitive;
if let Some(index) = u32::from_f64(value) {
return Self::Index(index);
}
Self::String(ryu_js::Buffer::new().format(value).into())
}
}
impl PartialEq<&str> for PropertyKey {
fn eq(&self, other: &&str) -> bool {
match self {
Self::String(ref string) => string == other,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, Finalize)]
pub(crate) enum PropertyNameKind {
Key,
Value,
KeyAndValue,
}
unsafe impl Trace for PropertyNameKind {
unsafe_empty_trace!();
}