From 9e1977a2f98648788b3d1f371e5641bb4136f745 Mon Sep 17 00:00:00 2001
From: Paulo Matos
Date: Fri, 3 Jul 2026 10:34:12 +0200
Subject: [PATCH] Adopt define_method for Map/Set/WeakMap/WeakSet prototype
builtins
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
setup_map/set/weakmap/weakset_prototype (plus WeakRef and
FinalizationRegistry) hand-rolled the create_function + insert_builtin
dance at 40 method call sites, even though define_method โ added for
atomics.rs and adopted for Array.prototype in 1d1152d โ already captures
that exact pattern. Switching collections.rs over removes ~200 lines of
repetition and brings the file in line with the one established
convention.
Behavior is unchanged: define_method installs the same
writable/non-enumerable/configurable own property the two-step dance did.
The reused iterator functions (Map.prototype.entries reused for
[@@iterator]; Set.prototype.values reused for keys and [@@iterator]) keep
their let-bindings so the aliasing is preserved; getters (size, species),
symbol-keyed installs, the constructor property, and the static
Map.groupBy are deliberately left untouched.
Adds collection_prototype_methods_have_correctly_shaped_builtins, a
JS-level characterization test pinning the name/length/descriptor shape
and functional behavior of every migrated method plus the iterator
aliasing, written before the refactor and left in place as a regression
guard.
Verified: cargo test (197/197), cargo clippy -D warnings, cargo fmt
--check, and test262 built-ins/{Map,Set,WeakMap,WeakSet,WeakRef,
FinalizationRegistry} (1772/1772, 100%) all green before and after. The
full-suite run shows 0 regressions attributable to this change โ the 22
baseline deltas are pre-existing intl402/DateTimeFormat ICU-data drift,
identical with this change stashed.
๐ค Generated with Claude Code
---
src/interpreter/builtins/collections.rs | 2192 ++++++++++-------------
src/interpreter/tests.rs | 88 +
2 files changed, 1037 insertions(+), 1243 deletions(-)
diff --git a/src/interpreter/builtins/collections.rs b/src/interpreter/builtins/collections.rs
index 85f1c12..04c1e12 100644
--- a/src/interpreter/builtins/collections.rs
+++ b/src/interpreter/builtins/collections.rs
@@ -28,78 +28,69 @@ impl Interpreter {
),
);
- let map_iter_next = self.create_function(JsFunction::native(
- "next".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object(o.id)
+ self.define_method(map_iter_proto_id, "next", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object(o.id)
+ {
+ let state = obj.borrow().iterator_state().cloned();
+ if let Some(IteratorState::MapIterator {
+ map_id,
+ index,
+ kind,
+ done,
+ }) = state
{
- let state = obj.borrow().iterator_state().cloned();
- if let Some(IteratorState::MapIterator {
- map_id,
- index,
- kind,
- done,
- }) = state
- {
- if done {
- return Completion::Normal(
- interp.create_iter_result_object(JsValue::Undefined, true),
- );
- }
- if let Some(map_obj) = interp.get_object(map_id) {
- let map_data = map_obj.borrow().map_data().cloned();
- if let Some(entries) = map_data {
- let mut i = index;
- while i < entries.len() {
- if let Some(ref entry) = entries[i] {
- let result = match kind {
- IteratorKind::Key => entry.0.clone(),
- IteratorKind::Value => entry.1.clone(),
- IteratorKind::KeyValue => interp.create_array(vec![
- entry.0.clone(),
- entry.1.clone(),
- ]),
- };
- obj.borrow_mut().kind =
- crate::interpreter::types::ObjectKind::Iterator(
- IteratorState::MapIterator {
- map_id,
- index: i + 1,
- kind,
- done: false,
- },
- );
- return Completion::Normal(
- interp.create_iter_result_object(result, false),
+ if done {
+ return Completion::Normal(
+ interp.create_iter_result_object(JsValue::Undefined, true),
+ );
+ }
+ if let Some(map_obj) = interp.get_object(map_id) {
+ let map_data = map_obj.borrow().map_data().cloned();
+ if let Some(entries) = map_data {
+ let mut i = index;
+ while i < entries.len() {
+ if let Some(ref entry) = entries[i] {
+ let result = match kind {
+ IteratorKind::Key => entry.0.clone(),
+ IteratorKind::Value => entry.1.clone(),
+ IteratorKind::KeyValue => interp
+ .create_array(vec![entry.0.clone(), entry.1.clone()]),
+ };
+ obj.borrow_mut().kind =
+ crate::interpreter::types::ObjectKind::Iterator(
+ IteratorState::MapIterator {
+ map_id,
+ index: i + 1,
+ kind,
+ done: false,
+ },
);
- }
- i += 1;
+ return Completion::Normal(
+ interp.create_iter_result_object(result, false),
+ );
}
+ i += 1;
}
}
- obj.borrow_mut().kind = crate::interpreter::types::ObjectKind::Iterator(
- IteratorState::MapIterator {
- map_id,
- index,
- kind,
- done: true,
- },
- );
- return Completion::Normal(
- interp.create_iter_result_object(JsValue::Undefined, true),
- );
}
+ obj.borrow_mut().kind = crate::interpreter::types::ObjectKind::Iterator(
+ IteratorState::MapIterator {
+ map_id,
+ index,
+ kind,
+ done: true,
+ },
+ );
+ return Completion::Normal(
+ interp.create_iter_result_object(JsValue::Undefined, true),
+ );
}
- let err =
- interp.create_type_error("Map Iterator.prototype.next requires a Map Iterator");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(map_iter_proto_id)
- .borrow_mut()
- .insert_builtin("next".to_string(), map_iter_next);
+ }
+ let err =
+ interp.create_type_error("Map Iterator.prototype.next requires a Map Iterator");
+ Completion::Throw(err)
+ });
if let Some(key) = self.get_symbol_iterator_key() {
let iter_self_fn = self.create_function(JsFunction::native(
@@ -142,30 +133,23 @@ impl Interpreter {
}
// Map.prototype.entries
- let entries_fn = self.create_function(JsFunction::native(
- "entries".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- && {
- let b = obj.borrow();
- b.map_data().is_some() && b.class_name == "Map"
- }
- {
- return Completion::Normal(create_map_iterator(
- interp,
- o.id,
- IteratorKind::KeyValue,
- ));
+ let entries_fn = self.define_method(proto_id, "entries", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ && {
+ let b = obj.borrow();
+ b.map_data().is_some() && b.class_name == "Map"
}
- let err = interp.create_type_error("Map.prototype.entries requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("entries".to_string(), entries_fn.clone());
+ {
+ return Completion::Normal(create_map_iterator(
+ interp,
+ o.id,
+ IteratorKind::KeyValue,
+ ));
+ }
+ let err = interp.create_type_error("Map.prototype.entries requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype[@@iterator] = entries
if let Some(key) = self.get_symbol_iterator_key() {
@@ -175,232 +159,171 @@ impl Interpreter {
}
// Map.prototype.keys
- let keys_fn = self.create_function(JsFunction::native(
- "keys".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- && {
- let b = obj.borrow();
- b.map_data().is_some() && b.class_name == "Map"
- }
- {
- return Completion::Normal(create_map_iterator(
- interp,
- o.id,
- IteratorKind::Key,
- ));
+ self.define_method(proto_id, "keys", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ && {
+ let b = obj.borrow();
+ b.map_data().is_some() && b.class_name == "Map"
}
- let err = interp.create_type_error("Map.prototype.keys requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("keys".to_string(), keys_fn);
+ {
+ return Completion::Normal(create_map_iterator(interp, o.id, IteratorKind::Key));
+ }
+ let err = interp.create_type_error("Map.prototype.keys requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.values
- let values_fn = self.create_function(JsFunction::native(
- "values".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- && {
- let b = obj.borrow();
- b.map_data().is_some() && b.class_name == "Map"
- }
- {
- return Completion::Normal(create_map_iterator(
- interp,
- o.id,
- IteratorKind::Value,
- ));
+ self.define_method(proto_id, "values", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ && {
+ let b = obj.borrow();
+ b.map_data().is_some() && b.class_name == "Map"
}
- let err = interp.create_type_error("Map.prototype.values requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("values".to_string(), values_fn);
+ {
+ return Completion::Normal(create_map_iterator(interp, o.id, IteratorKind::Value));
+ }
+ let err = interp.create_type_error("Map.prototype.values requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.get
- let get_fn = self.create_function(JsFunction::native(
- "get".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let borrowed = obj.borrow();
- let is_map = borrowed.map_data().is_some() && borrowed.class_name == "Map";
- let map_data = if is_map {
- borrowed.map_data().cloned()
- } else {
- None
- };
- drop(borrowed);
- if let Some(entries) = map_data {
- let key = args.first().cloned().unwrap_or(JsValue::Undefined);
- for entry in entries.iter().flatten() {
- if same_value_zero(&entry.0, &key) {
- return Completion::Normal(entry.1.clone());
- }
+ self.define_method(proto_id, "get", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let borrowed = obj.borrow();
+ let is_map = borrowed.map_data().is_some() && borrowed.class_name == "Map";
+ let map_data = if is_map {
+ borrowed.map_data().cloned()
+ } else {
+ None
+ };
+ drop(borrowed);
+ if let Some(entries) = map_data {
+ let key = args.first().cloned().unwrap_or(JsValue::Undefined);
+ for entry in entries.iter().flatten() {
+ if same_value_zero(&entry.0, &key) {
+ return Completion::Normal(entry.1.clone());
}
- return Completion::Normal(JsValue::Undefined);
}
+ return Completion::Normal(JsValue::Undefined);
}
- let err = interp.create_type_error("Map.prototype.get requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("get".to_string(), get_fn);
+ }
+ let err = interp.create_type_error("Map.prototype.get requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.set
- let set_fn = self.create_function(JsFunction::native(
- "set".to_string(),
- 2,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let has_map = {
- let b = obj.borrow();
- b.map_data().is_some() && b.class_name == "Map"
- };
- if has_map {
- let mut key = args.first().cloned().unwrap_or(JsValue::Undefined);
- let value = args.get(1).cloned().unwrap_or(JsValue::Undefined);
- // Normalize -0 to +0
- if let JsValue::Number(n) = &key
- && *n == 0.0
- && n.is_sign_negative()
- {
- key = JsValue::Number(0.0);
- }
- let mut borrowed = obj.borrow_mut();
- let entries = borrowed.map_data_mut().unwrap();
- for entry in entries.iter_mut().flatten() {
- if same_value_zero(&entry.0, &key) {
- entry.1 = value;
- return Completion::Normal(this.clone());
- }
+ self.define_method(proto_id, "set", 2, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let has_map = {
+ let b = obj.borrow();
+ b.map_data().is_some() && b.class_name == "Map"
+ };
+ if has_map {
+ let mut key = args.first().cloned().unwrap_or(JsValue::Undefined);
+ let value = args.get(1).cloned().unwrap_or(JsValue::Undefined);
+ // Normalize -0 to +0
+ if let JsValue::Number(n) = &key
+ && *n == 0.0
+ && n.is_sign_negative()
+ {
+ key = JsValue::Number(0.0);
+ }
+ let mut borrowed = obj.borrow_mut();
+ let entries = borrowed.map_data_mut().unwrap();
+ for entry in entries.iter_mut().flatten() {
+ if same_value_zero(&entry.0, &key) {
+ entry.1 = value;
+ return Completion::Normal(this.clone());
}
- entries.push(Some((key, value)));
- return Completion::Normal(this.clone());
}
+ entries.push(Some((key, value)));
+ return Completion::Normal(this.clone());
}
- let err = interp.create_type_error("Map.prototype.set requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("set".to_string(), set_fn);
+ }
+ let err = interp.create_type_error("Map.prototype.set requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.has
- let has_fn = self.create_function(JsFunction::native(
- "has".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let borrowed = obj.borrow();
- let is_map = borrowed.map_data().is_some() && borrowed.class_name == "Map";
- let map_data = if is_map {
- borrowed.map_data().cloned()
- } else {
- None
- };
- drop(borrowed);
- if let Some(entries) = map_data {
- let key = args.first().cloned().unwrap_or(JsValue::Undefined);
- for entry in entries.iter().flatten() {
- if same_value_zero(&entry.0, &key) {
- return Completion::Normal(JsValue::Boolean(true));
- }
+ self.define_method(proto_id, "has", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let borrowed = obj.borrow();
+ let is_map = borrowed.map_data().is_some() && borrowed.class_name == "Map";
+ let map_data = if is_map {
+ borrowed.map_data().cloned()
+ } else {
+ None
+ };
+ drop(borrowed);
+ if let Some(entries) = map_data {
+ let key = args.first().cloned().unwrap_or(JsValue::Undefined);
+ for entry in entries.iter().flatten() {
+ if same_value_zero(&entry.0, &key) {
+ return Completion::Normal(JsValue::Boolean(true));
}
- return Completion::Normal(JsValue::Boolean(false));
}
+ return Completion::Normal(JsValue::Boolean(false));
}
- let err = interp.create_type_error("Map.prototype.has requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("has".to_string(), has_fn);
+ }
+ let err = interp.create_type_error("Map.prototype.has requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.delete
- let delete_fn = self.create_function(JsFunction::native(
- "delete".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let has_map = {
- let b = obj.borrow();
- b.map_data().is_some() && b.class_name == "Map"
- };
- if has_map {
- let key = args.first().cloned().unwrap_or(JsValue::Undefined);
- let mut borrowed = obj.borrow_mut();
- let entries = borrowed.map_data_mut().unwrap();
- for entry in entries.iter_mut() {
- let matches =
- entry.as_ref().is_some_and(|e| same_value_zero(&e.0, &key));
- if matches {
- *entry = None;
- return Completion::Normal(JsValue::Boolean(true));
- }
- }
- return Completion::Normal(JsValue::Boolean(false));
- }
+ self.define_method(proto_id, "delete", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let has_map = {
+ let b = obj.borrow();
+ b.map_data().is_some() && b.class_name == "Map"
+ };
+ if has_map {
+ let key = args.first().cloned().unwrap_or(JsValue::Undefined);
+ let mut borrowed = obj.borrow_mut();
+ let entries = borrowed.map_data_mut().unwrap();
+ for entry in entries.iter_mut() {
+ let matches = entry.as_ref().is_some_and(|e| same_value_zero(&e.0, &key));
+ if matches {
+ *entry = None;
+ return Completion::Normal(JsValue::Boolean(true));
+ }
+ }
+ return Completion::Normal(JsValue::Boolean(false));
}
- let err = interp.create_type_error("Map.prototype.delete requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("delete".to_string(), delete_fn);
+ }
+ let err = interp.create_type_error("Map.prototype.delete requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.clear
- let clear_fn = self.create_function(JsFunction::native(
- "clear".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let has_map = {
- let b = obj.borrow();
- b.map_data().is_some() && b.class_name == "Map"
- };
- if has_map {
- obj.borrow_mut().kind =
- crate::interpreter::types::ObjectKind::Map(Vec::new());
- return Completion::Normal(JsValue::Undefined);
- }
+ self.define_method(proto_id, "clear", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let has_map = {
+ let b = obj.borrow();
+ b.map_data().is_some() && b.class_name == "Map"
+ };
+ if has_map {
+ obj.borrow_mut().kind = crate::interpreter::types::ObjectKind::Map(Vec::new());
+ return Completion::Normal(JsValue::Undefined);
}
- let err = interp.create_type_error("Map.prototype.clear requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("clear".to_string(), clear_fn);
+ }
+ let err = interp.create_type_error("Map.prototype.clear requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.forEach
- let foreach_fn = self.create_function(JsFunction::native(
- "forEach".to_string(),
- 1,
+ self.define_method(proto_id, "forEach", 1,
|interp, this, args| {
if let JsValue::Object(o) = this
&& let Some(obj) = interp.get_object(o.id) {
@@ -432,10 +355,7 @@ impl Interpreter {
let err = interp.create_type_error("Map.prototype.forEach requires a Map");
Completion::Throw(err)
},
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("forEach".to_string(), foreach_fn);
+ );
// Map.prototype.size (getter)
let size_getter = self.create_function(JsFunction::native(
@@ -477,56 +397,47 @@ impl Interpreter {
);
// Map.prototype.getOrInsert
- let get_or_insert_fn = self.create_function(JsFunction::native(
- "getOrInsert".to_string(),
- 2,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let is_map = {
+ self.define_method(proto_id, "getOrInsert", 2, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let is_map = {
+ let borrowed = obj.borrow();
+ borrowed.map_data().is_some() && borrowed.class_name == "Map"
+ };
+ if is_map {
+ let mut key = args.first().cloned().unwrap_or(JsValue::Undefined);
+ let value = args.get(1).cloned().unwrap_or(JsValue::Undefined);
+ // CanonicalizeKeyedCollectionKey: normalize -0 to +0
+ if let JsValue::Number(n) = &key
+ && *n == 0.0
+ && n.is_sign_negative()
+ {
+ key = JsValue::Number(0.0);
+ }
+ // Search existing entries
+ {
let borrowed = obj.borrow();
- borrowed.map_data().is_some() && borrowed.class_name == "Map"
- };
- if is_map {
- let mut key = args.first().cloned().unwrap_or(JsValue::Undefined);
- let value = args.get(1).cloned().unwrap_or(JsValue::Undefined);
- // CanonicalizeKeyedCollectionKey: normalize -0 to +0
- if let JsValue::Number(n) = &key
- && *n == 0.0
- && n.is_sign_negative()
- {
- key = JsValue::Number(0.0);
- }
- // Search existing entries
- {
- let borrowed = obj.borrow();
- let entries = borrowed.map_data().unwrap();
- for entry in entries.iter().flatten() {
- if same_value_zero(&entry.0, &key) {
- return Completion::Normal(entry.1.clone());
- }
+ let entries = borrowed.map_data().unwrap();
+ for entry in entries.iter().flatten() {
+ if same_value_zero(&entry.0, &key) {
+ return Completion::Normal(entry.1.clone());
}
}
- // Key not found - append new entry
- let mut borrowed = obj.borrow_mut();
- let entries = borrowed.map_data_mut().unwrap();
- entries.push(Some((key, value.clone())));
- return Completion::Normal(value);
}
+ // Key not found - append new entry
+ let mut borrowed = obj.borrow_mut();
+ let entries = borrowed.map_data_mut().unwrap();
+ entries.push(Some((key, value.clone())));
+ return Completion::Normal(value);
}
- let err = interp.create_type_error("Map.prototype.getOrInsert requires a Map");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("getOrInsert".to_string(), get_or_insert_fn);
+ }
+ let err = interp.create_type_error("Map.prototype.getOrInsert requires a Map");
+ Completion::Throw(err)
+ });
// Map.prototype.getOrInsertComputed
- let get_or_insert_computed_fn = self.create_function(JsFunction::native(
- "getOrInsertComputed".to_string(),
- 2,
+ self.define_method(proto_id, "getOrInsertComputed", 2,
|interp, this, args| {
if let JsValue::Object(o) = this
&& let Some(obj) = interp.get_object_cell(o.id)
@@ -583,10 +494,7 @@ impl Interpreter {
let err = interp.create_type_error("Map.prototype.getOrInsertComputed requires a Map");
Completion::Throw(err)
},
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("getOrInsertComputed".to_string(), get_or_insert_computed_fn);
+ );
// @@toStringTag
self.get_object_cell_expect(proto_id)
@@ -912,76 +820,69 @@ impl Interpreter {
),
);
- let set_iter_next = self.create_function(JsFunction::native(
- "next".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object(o.id)
+ self.define_method(set_iter_proto_id, "next", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object(o.id)
+ {
+ let state = obj.borrow().iterator_state().cloned();
+ if let Some(IteratorState::SetIterator {
+ set_id,
+ index,
+ kind,
+ done,
+ }) = state
{
- let state = obj.borrow().iterator_state().cloned();
- if let Some(IteratorState::SetIterator {
- set_id,
- index,
- kind,
- done,
- }) = state
- {
- if done {
- return Completion::Normal(
- interp.create_iter_result_object(JsValue::Undefined, true),
- );
- }
- if let Some(set_obj) = interp.get_object(set_id) {
- let set_data = set_obj.borrow().set_data().cloned();
- if let Some(entries) = set_data {
- let mut i = index;
- while i < entries.len() {
- if let Some(ref val) = entries[i] {
- let result = match kind {
- IteratorKind::Value | IteratorKind::Key => val.clone(),
- IteratorKind::KeyValue => {
- interp.create_array(vec![val.clone(), val.clone()])
- }
- };
- obj.borrow_mut().kind =
- crate::interpreter::types::ObjectKind::Iterator(
- IteratorState::SetIterator {
- set_id,
- index: i + 1,
- kind,
- done: false,
- },
- );
- return Completion::Normal(
- interp.create_iter_result_object(result, false),
- );
- }
- i += 1;
- }
- }
- }
- obj.borrow_mut().kind = crate::interpreter::types::ObjectKind::Iterator(
- IteratorState::SetIterator {
- set_id,
- index,
- kind,
- done: true,
- },
- );
+ if done {
return Completion::Normal(
interp.create_iter_result_object(JsValue::Undefined, true),
);
}
+ if let Some(set_obj) = interp.get_object(set_id) {
+ let set_data = set_obj.borrow().set_data().cloned();
+ if let Some(entries) = set_data {
+ let mut i = index;
+ while i < entries.len() {
+ if let Some(ref val) = entries[i] {
+ let result = match kind {
+ IteratorKind::Value | IteratorKind::Key => val.clone(),
+ IteratorKind::KeyValue => {
+ interp.create_array(vec![val.clone(), val.clone()])
+ }
+ };
+ obj.borrow_mut().kind =
+ crate::interpreter::types::ObjectKind::Iterator(
+ IteratorState::SetIterator {
+ set_id,
+ index: i + 1,
+ kind,
+ done: false,
+ },
+ );
+ return Completion::Normal(
+ interp.create_iter_result_object(result, false),
+ );
+ }
+ i += 1;
+ }
+ }
+ }
+ obj.borrow_mut().kind = crate::interpreter::types::ObjectKind::Iterator(
+ IteratorState::SetIterator {
+ set_id,
+ index,
+ kind,
+ done: true,
+ },
+ );
+ return Completion::Normal(
+ interp.create_iter_result_object(JsValue::Undefined, true),
+ );
}
- let err =
- interp.create_type_error("Set Iterator.prototype.next requires a Set Iterator");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(set_iter_proto_id)
- .borrow_mut()
- .insert_builtin("next".to_string(), set_iter_next);
+ }
+ let err =
+ interp.create_type_error("Set Iterator.prototype.next requires a Set Iterator");
+ Completion::Throw(err)
+ });
if let Some(key) = self.get_symbol_iterator_key() {
let iter_self_fn = self.create_function(JsFunction::native(
@@ -1023,30 +924,19 @@ impl Interpreter {
}
// Set.prototype.values
- let values_fn = self.create_function(JsFunction::native(
- "values".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- && {
- let b = obj.borrow();
- b.set_data().is_some() && b.class_name != "WeakSet"
- }
- {
- return Completion::Normal(create_set_iterator(
- interp,
- o.id,
- IteratorKind::Value,
- ));
+ let values_fn = self.define_method(proto_id, "values", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ && {
+ let b = obj.borrow();
+ b.set_data().is_some() && b.class_name != "WeakSet"
}
- let err = interp.create_type_error("Set.prototype.values requires a Set");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("values".to_string(), values_fn.clone());
+ {
+ return Completion::Normal(create_set_iterator(interp, o.id, IteratorKind::Value));
+ }
+ let err = interp.create_type_error("Set.prototype.values requires a Set");
+ Completion::Throw(err)
+ });
// Set.prototype.keys = Set.prototype.values
self.get_object_cell_expect(proto_id)
@@ -1061,168 +951,129 @@ impl Interpreter {
}
// Set.prototype.entries
- let entries_fn = self.create_function(JsFunction::native(
- "entries".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- && {
- let b = obj.borrow();
- b.set_data().is_some() && b.class_name != "WeakSet"
- }
- {
- return Completion::Normal(create_set_iterator(
- interp,
- o.id,
- IteratorKind::KeyValue,
- ));
+ self.define_method(proto_id, "entries", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ && {
+ let b = obj.borrow();
+ b.set_data().is_some() && b.class_name != "WeakSet"
}
- let err = interp.create_type_error("Set.prototype.entries requires a Set");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("entries".to_string(), entries_fn);
+ {
+ return Completion::Normal(create_set_iterator(
+ interp,
+ o.id,
+ IteratorKind::KeyValue,
+ ));
+ }
+ let err = interp.create_type_error("Set.prototype.entries requires a Set");
+ Completion::Throw(err)
+ });
// Set.prototype.add
- let add_fn = self.create_function(JsFunction::native(
- "add".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let has_set = {
- let b = obj.borrow();
- b.set_data().is_some() && b.class_name != "WeakSet"
- };
- if has_set {
- let mut value = args.first().cloned().unwrap_or(JsValue::Undefined);
- if let JsValue::Number(n) = &value
- && *n == 0.0
- && n.is_sign_negative()
- {
- value = JsValue::Number(0.0);
- }
- let mut borrowed = obj.borrow_mut();
- let entries = borrowed.set_data_mut().unwrap();
- for entry in entries.iter().flatten() {
- if same_value_zero(entry, &value) {
- return Completion::Normal(this.clone());
- }
+ self.define_method(proto_id, "add", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let has_set = {
+ let b = obj.borrow();
+ b.set_data().is_some() && b.class_name != "WeakSet"
+ };
+ if has_set {
+ let mut value = args.first().cloned().unwrap_or(JsValue::Undefined);
+ if let JsValue::Number(n) = &value
+ && *n == 0.0
+ && n.is_sign_negative()
+ {
+ value = JsValue::Number(0.0);
+ }
+ let mut borrowed = obj.borrow_mut();
+ let entries = borrowed.set_data_mut().unwrap();
+ for entry in entries.iter().flatten() {
+ if same_value_zero(entry, &value) {
+ return Completion::Normal(this.clone());
}
- entries.push(Some(value));
- return Completion::Normal(this.clone());
}
+ entries.push(Some(value));
+ return Completion::Normal(this.clone());
}
- let err = interp.create_type_error("Set.prototype.add requires a Set");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("add".to_string(), add_fn);
+ }
+ let err = interp.create_type_error("Set.prototype.add requires a Set");
+ Completion::Throw(err)
+ });
// Set.prototype.has
- let has_fn = self.create_function(JsFunction::native(
- "has".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let (is_set, set_data) = {
- let b = obj.borrow();
- (
- b.set_data().is_some() && b.class_name != "WeakSet",
- b.set_data().cloned(),
- )
- };
- if is_set && let Some(entries) = set_data {
- let value = args.first().cloned().unwrap_or(JsValue::Undefined);
- for entry in entries.iter().flatten() {
- if same_value_zero(entry, &value) {
- return Completion::Normal(JsValue::Boolean(true));
- }
+ self.define_method(proto_id, "has", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let (is_set, set_data) = {
+ let b = obj.borrow();
+ (
+ b.set_data().is_some() && b.class_name != "WeakSet",
+ b.set_data().cloned(),
+ )
+ };
+ if is_set && let Some(entries) = set_data {
+ let value = args.first().cloned().unwrap_or(JsValue::Undefined);
+ for entry in entries.iter().flatten() {
+ if same_value_zero(entry, &value) {
+ return Completion::Normal(JsValue::Boolean(true));
}
- return Completion::Normal(JsValue::Boolean(false));
}
+ return Completion::Normal(JsValue::Boolean(false));
}
- let err = interp.create_type_error("Set.prototype.has requires a Set");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("has".to_string(), has_fn);
+ }
+ let err = interp.create_type_error("Set.prototype.has requires a Set");
+ Completion::Throw(err)
+ });
// Set.prototype.delete
- let delete_fn = self.create_function(JsFunction::native(
- "delete".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let has_set = {
- let b = obj.borrow();
- b.set_data().is_some() && b.class_name != "WeakSet"
- };
- if has_set {
- let value = args.first().cloned().unwrap_or(JsValue::Undefined);
- let mut borrowed = obj.borrow_mut();
- let entries = borrowed.set_data_mut().unwrap();
- for entry in entries.iter_mut() {
- let matches =
- entry.as_ref().is_some_and(|e| same_value_zero(e, &value));
- if matches {
- *entry = None;
- return Completion::Normal(JsValue::Boolean(true));
- }
- }
- return Completion::Normal(JsValue::Boolean(false));
- }
+ self.define_method(proto_id, "delete", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let has_set = {
+ let b = obj.borrow();
+ b.set_data().is_some() && b.class_name != "WeakSet"
+ };
+ if has_set {
+ let value = args.first().cloned().unwrap_or(JsValue::Undefined);
+ let mut borrowed = obj.borrow_mut();
+ let entries = borrowed.set_data_mut().unwrap();
+ for entry in entries.iter_mut() {
+ let matches = entry.as_ref().is_some_and(|e| same_value_zero(e, &value));
+ if matches {
+ *entry = None;
+ return Completion::Normal(JsValue::Boolean(true));
+ }
+ }
+ return Completion::Normal(JsValue::Boolean(false));
}
- let err = interp.create_type_error("Set.prototype.delete requires a Set");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("delete".to_string(), delete_fn);
+ }
+ let err = interp.create_type_error("Set.prototype.delete requires a Set");
+ Completion::Throw(err)
+ });
// Set.prototype.clear
- let clear_fn = self.create_function(JsFunction::native(
- "clear".to_string(),
- 0,
- |interp, this, _args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object_cell(o.id)
- {
- let has_set = {
- let b = obj.borrow();
- b.set_data().is_some() && b.class_name != "WeakSet"
- };
- if has_set {
- obj.borrow_mut().kind =
- crate::interpreter::types::ObjectKind::Set(Vec::new());
- return Completion::Normal(JsValue::Undefined);
- }
+ self.define_method(proto_id, "clear", 0, |interp, this, _args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object_cell(o.id)
+ {
+ let has_set = {
+ let b = obj.borrow();
+ b.set_data().is_some() && b.class_name != "WeakSet"
+ };
+ if has_set {
+ obj.borrow_mut().kind = crate::interpreter::types::ObjectKind::Set(Vec::new());
+ return Completion::Normal(JsValue::Undefined);
}
- let err = interp.create_type_error("Set.prototype.clear requires a Set");
- Completion::Throw(err)
- },
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("clear".to_string(), clear_fn);
+ }
+ let err = interp.create_type_error("Set.prototype.clear requires a Set");
+ Completion::Throw(err)
+ });
// Set.prototype.forEach
- let foreach_fn = self.create_function(JsFunction::native(
- "forEach".to_string(),
- 1,
+ self.define_method(proto_id, "forEach", 1,
|interp, this, args| {
if let JsValue::Object(o) = this
&& let Some(obj) = interp.get_object(o.id) {
@@ -1254,10 +1105,7 @@ impl Interpreter {
let err = interp.create_type_error("Set.prototype.forEach requires a Set");
Completion::Throw(err)
},
- ));
- self.get_object_cell_expect(proto_id)
- .borrow_mut()
- .insert_builtin("forEach".to_string(), foreach_fn);
+ );
// Set.prototype.size (getter)
let size_getter = self.create_function(JsFunction::native(
@@ -1441,289 +1289,344 @@ impl Interpreter {
}
// Set.prototype.union
- let union_fn = self.create_function(JsFunction::native(
- "union".to_string(),
- 1,
- |interp, this, args| {
- if let JsValue::Object(o) = this
- && let Some(obj) = interp.get_object(o.id)
- && {
- let b = obj.borrow();
- b.set_data().is_some() && b.class_name != "WeakSet"
- }
+ self.define_method(proto_id, "union", 1, |interp, this, args| {
+ if let JsValue::Object(o) = this
+ && let Some(obj) = interp.get_object(o.id)
+ && {
+ let b = obj.borrow();
+ b.set_data().is_some() && b.class_name != "WeakSet"
+ }
+ {
+ let other = args.first().cloned().unwrap_or(JsValue::Undefined);
+ let other_rec = match spec_get_set_record(interp, &other) {
+ Ok(r) => r,
+ Err(e) => return Completion::Throw(e),
+ };
+ // Step 5: GetIteratorFromMethod (may trigger .next getter side effects)
+ let (keys_iter, next_fn) = match get_keys_iterator(interp, &other_rec.keys, &other)
{
- let other = args.first().cloned().unwrap_or(JsValue::Undefined);
- let other_rec = match spec_get_set_record(interp, &other) {
- Ok(r) => r,
- Err(e) => return Completion::Throw(e),
+ Ok(r) => r,
+ Err(c) => return c,
+ };
+ // Step 7: Copy O.[[SetData]] AFTER GetIteratorFromMethod
+ let entries = obj.borrow().set_data().cloned().unwrap();
+ let mut new_entries: Vec