Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions core/engine/src/builtins/array/array_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,7 @@ pub(crate) struct ArrayIterator {
impl IntrinsicObject for ArrayIterator {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(
realm
.intrinsics()
.objects()
.iterator_prototypes()
.iterator(),
)
.prototype(realm.intrinsics().constructors().iterator().prototype())
.static_method(Self::next, js_string!("next"), 0)
.static_property(
JsSymbol::to_string_tag(),
Expand Down
199 changes: 125 additions & 74 deletions core/engine/src/builtins/array/from_async.rs

Large diffs are not rendered by default.

119 changes: 42 additions & 77 deletions core/engine/src/builtins/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,6 @@ pub(crate) struct BuiltInConstructorWithPrototype<'ctx> {
__proto__: JsPrototype,
inherits: Option<JsObject>,
attributes: Attribute,
/// If `Some`, the `constructor` property on the prototype will be installed
/// as a get/set accessor pair instead of as a writable data property.
/// This is needed by `Iterator.prototype.constructor` per the spec
/// (web-compat requirement).
constructor_accessor: Option<(JsFunction, JsFunction)>,
}

#[allow(dead_code)]
Expand All @@ -199,13 +194,6 @@ impl BuiltInConstructorWithPrototype<'_> {
/// See [`BuiltInConstructorWithPrototype::build`].
const OWN_PROTOTYPE_STORAGE_SLOTS: usize = 1;

/// The number of storage slots properties that are always present in a
/// standard constructor's prototype object when `constructor` is installed
/// as a get/set **accessor** property (two slots: getter + setter).
///
/// See [`BuiltInConstructorWithPrototype::constructor_accessor`].
const OWN_PROTOTYPE_STORAGE_SLOTS_ACCESSOR: usize = 2;

/// Specify how many arguments the constructor function takes.
///
/// Default is `0`.
Expand Down Expand Up @@ -390,25 +378,6 @@ impl BuiltInConstructorWithPrototype<'_> {
self
}

/// Installs `Iterator.prototype.constructor` (or any analogous property) as a
/// configurable, non-enumerable **accessor** descriptor instead of the default
/// writable data property.
///
/// Per the [ECMAScript spec][spec], `Iterator.prototype.constructor` must be an
/// accessor for web-compatibility reasons: previously the property did not exist, so
/// making it a setter that ignores writes to the prototype avoids breaking existing code
/// that assigns `Iterator.prototype.constructor = ...`.
///
/// When this method is called the `PROTOTYPE_STORAGE_SLOTS` constant on the
/// implementing built-in **must** account for two extra slots (getter + setter)
/// instead of the usual one slot for a data property.
///
/// [spec]: https://tc39.es/ecma262/#sec-iterator.prototype.constructor
pub(crate) fn constructor_accessor(mut self, get: JsFunction, set: JsFunction) -> Self {
self.constructor_accessor = Some((get, set));
self
}

#[track_caller]
pub(crate) fn build(mut self) {
let length = self.length;
Expand All @@ -418,52 +387,19 @@ impl BuiltInConstructorWithPrototype<'_> {
self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
self = self.static_property(PROTOTYPE, prototype, Attribute::empty());

// Install the `constructor` property on the prototype — either as a writable
// data property (the common case) or as a get/set accessor (needed by
// `Iterator.prototype.constructor` for web-compat, see `constructor_accessor`).
if let Some((get, set)) = self.constructor_accessor.take() {
// Accessor path: two storage slots (getter + setter). The `CONSTRUCTOR` key
// must NOT already be present (no duplicate insertion).
let mut attributes = SlotAttributes::CONFIGURABLE;
attributes.set(SlotAttributes::GET, true);
attributes.set(SlotAttributes::SET, true);
debug_assert!(
!self
.prototype_property_table
.map
.contains_key(&CONSTRUCTOR.into())
);
self.prototype_property_table
.insert(CONSTRUCTOR.into(), attributes);
self.prototype_storage
.extend([JsValue::new(get), JsValue::new(set)]);
#[cfg(debug_assertions)]
assert!(
self.prototype_storage.len()
<= self.prototype_storage_slots_expected
+ Self::OWN_PROTOTYPE_STORAGE_SLOTS_ACCESSOR,
"expected to allocate at most {} prototype storage slots, got {}. \
constant {}::PROTOTYPE_STORAGE_SLOTS may need to be adjusted",
self.prototype_storage_slots_expected,
self.prototype_storage.len() - Self::OWN_PROTOTYPE_STORAGE_SLOTS_ACCESSOR,
self.name.display_escaped(),
);
} else {
// Data property path (the default).
let attributes = self.attributes;
let object = self.constructor.clone();
self = self.property(CONSTRUCTOR, object, attributes);
#[cfg(debug_assertions)]
assert!(
self.prototype_storage.len()
<= self.prototype_storage_slots_expected + Self::OWN_PROTOTYPE_STORAGE_SLOTS,
"expected to allocate at most {} prototype storage slots, got {}. \
let attributes = self.attributes;
let object = self.constructor.clone();
self = self.property(CONSTRUCTOR, object, attributes);
#[cfg(debug_assertions)]
assert!(
self.prototype_storage.len()
<= self.prototype_storage_slots_expected + Self::OWN_PROTOTYPE_STORAGE_SLOTS,
"expected to allocate at most {} prototype storage slots, got {}. \
constant {}::PROTOTYPE_STORAGE_SLOTS may need to be adjusted",
self.prototype_storage_slots_expected,
self.prototype_storage.len() - Self::OWN_PROTOTYPE_STORAGE_SLOTS,
self.name.display_escaped(),
);
}
self.prototype_storage_slots_expected,
self.prototype_storage.len() - Self::OWN_PROTOTYPE_STORAGE_SLOTS,
self.name.display_escaped(),
);

#[cfg(debug_assertions)]
assert!(
Expand Down Expand Up @@ -682,7 +618,6 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
__proto__: Some(realm.intrinsics().constructors().function().prototype()),
inherits: Some(realm.intrinsics().constructors().object().prototype()),
attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
constructor_accessor: None,
}
}
}
Expand Down Expand Up @@ -730,6 +665,36 @@ impl<T> BuiltInBuilder<'_, T> {
self
}

/// Adds a new accessor property to the builtin object.
pub(crate) fn static_accessor<K>(
self,
key: K,
get: Option<JsFunction>,
set: Option<JsFunction>,
attribute: Attribute,
) -> Self
where
K: Into<PropertyKey>,
{
let mut property = PropertyDescriptor::builder()
.enumerable(attribute.enumerable())
.configurable(attribute.configurable());

if let Some(get) = get {
property = property.get(get);
}

if let Some(set) = set {
property = property.set(set);
}

let key = key.into();

self.object.insert(key, property);

self
}

/// Specify the `[[Prototype]]` internal field of the builtin object.
///
/// Default is `Function.prototype` for constructors and `Object.prototype` for statics.
Expand Down
8 changes: 1 addition & 7 deletions core/engine/src/builtins/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,7 @@ pub struct Generator {
impl IntrinsicObject for Generator {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(
realm
.intrinsics()
.objects()
.iterator_prototypes()
.iterator(),
)
.prototype(realm.intrinsics().constructors().iterator().prototype())
.static_method(Self::next, js_string!("next"), 1)
.static_method(Self::r#return, js_string!("return"), 1)
.static_method(Self::throw, js_string!("throw"), 1)
Expand Down
Loading
Loading