Skip to content
Open
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
1 change: 1 addition & 0 deletions nova_vm/src/builtin_strings
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ isNaN
isPrototypeOf
isSafeInteger
isSealed
isSubsetOf
#[cfg(feature = "array-buffer")]isView
isWellFormed
#[cfg(feature = "annex-b-string")]italics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

mod abstract_operations;
pub(crate) use abstract_operations::*;
mod set_constructor;
pub(crate) use set_constructor::*;
mod set_iterator_objects;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::{
ecmascript::{
Agent, BUILTIN_STRING_MEMORY,
ExceptionType::{self},
Function, IntegerOrInfinity, JsResult, Object, Value, get, is_callable,
to_integer_or_infinity_number, to_number,
},
engine::{Bindable, GcScope, Scopable, bindable_handle},
};

/// ### [24.2.1.1 Set Records](https://tc39.es/ecma262/#sec-set-records)
///
/// A Set Record is a Record used to encapsulate the interface of a Set or
/// similar object.
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetRecord<'a> {
pub(crate) set_object: Object<'a>,
pub(crate) size: IntegerOrInfinity,
pub(crate) has: Function<'a>,
#[expect(dead_code, reason = "used by upcoming Set methods")]
pub(crate) keys: Function<'a>,
}

bindable_handle!(SetRecord);

/// ### [24.2.1.2 GetSetRecord ( obj )](https://tc39.es/ecma262/#sec-getsetrecord)
///
/// The abstract operation GetSetRecord takes argument obj (an ECMAScript
/// language value) and returns either a normal completion containing a Set
/// Record or a throw completion.
pub(crate) fn get_set_record<'a>(
agent: &mut Agent,
obj: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, SetRecord<'a>> {
let obj = obj.bind(gc.nogc());

// 1. If obj is not an Object, throw a TypeError exception.
let Ok(obj) = Object::try_from(obj) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Value is not an object",
gc.into_nogc(),
));
};
// 2. Let rawSize be ? Get(obj, "size").
let scoped_obj = obj.scope(agent, gc.nogc());
let raw_size = get(
agent,
obj.unbind(),
BUILTIN_STRING_MEMORY.size.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
// 3. Let numberSize be ? ToNumber(rawSize).
let number_size = to_number(agent, raw_size.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
// 4. NOTE: If rawSize is undefined, then numberSize will be NaN.
// 5. If numberSize is NaN, throw a TypeError exception.
if number_size.is_nan(agent) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Set size is NaN",
gc.into_nogc(),
));
};
// 6. Let intSize be ! ToIntegerOrInfinity(numberSize).
let int_size = to_integer_or_infinity_number(agent, number_size);
// 7. If intSize < 0, throw a RangeError exception.
if int_size < 0 {
return Err(agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"Size is less than 0",
gc.into_nogc(),
));
}
// 8. Let has be ? Get(obj, "has").
let has_fn = get(
agent,
scoped_obj.get(agent),
BUILTIN_STRING_MEMORY.has.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
// 9. If IsCallable(has) is false, throw a TypeError exception.
let Some(has_fn) = is_callable(has_fn, gc.nogc()) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Has is not callable",
gc.into_nogc(),
));
};
let scoped_has_fn = has_fn.scope(agent, gc.nogc());
// 10. Let keys be ? Get(obj, "keys").
let keys_fn = get(
agent,
scoped_obj.get(agent),
BUILTIN_STRING_MEMORY.keys.into(),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
// 11. If IsCallable(keys) is false, throw a TypeError exception.
let Some(keys_fn) = is_callable(keys_fn, gc) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Keys is not callable",
gc,
));
};
// 12. Return a new Set Record { [[SetObject]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }.
Ok(SetRecord {
set_object: scoped_obj.get(agent).bind(gc),
size: int_size,
has: scoped_has_fn.get(agent).bind(gc),
keys: keys_fn,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter,
BuiltinIntrinsic, CollectionIteratorKind, ExceptionType, JsResult, Number, PropertyKey,
Realm, Set, SetIterator, String, Value, builders::OrdinaryObjectBuilder, call_function,
canonicalize_keyed_collection_key, is_callable, same_value,
canonicalize_keyed_collection_key, get_set_record, is_callable, same_value, to_boolean,
},
engine::{Bindable, GcScope, NoGcScope, Scopable},
heap::{
Expand Down Expand Up @@ -58,6 +58,12 @@ impl Builtin for SetPrototypeHas {
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(SetPrototype::has);
}
struct SetPrototypeIsSubsetOf;
impl Builtin for SetPrototypeIsSubsetOf {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.isSubsetOf;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(SetPrototype::is_subset_of);
}
struct SetPrototypeGetSize;
impl Builtin for SetPrototypeGetSize {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.get_size;
Expand Down Expand Up @@ -384,6 +390,68 @@ impl SetPrototype {
Ok(found.into())
}

/// ### [24.2.4.11 Set.prototype.isSubsetOf ( other )](https://tc39.es/ecma262/#sec-set.prototype.issubsetof)
fn is_subset_of<'gc>(
agent: &mut Agent,
this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let other = arguments.get(0).bind(gc.nogc());
// 1. Let set be the this value.
let set = this_value.bind(gc.nogc());
// 2. Perform ? RequireInternalSlot(set, [[SetData]]).
let set = require_set_data_internal_slot(agent, set, gc.nogc())
.unbind()?
.bind(gc.nogc());
let scoped_set = set.scope(agent, gc.nogc());
// 3. Let otherRecord be ? GetSetRecord(other).
let other_record = get_set_record(agent, other.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
// 4. If SetDataSize(set.[[SetData]]) > otherRecord.[[Size]], return false.
let set_size = scoped_set.get(agent).get(agent).set_data.borrow().len() as i64;
if set_size > other_record.size.into_i64() {
return Ok(false.into());
}
// 5. Let thisSize be the number of elements in set.[[SetData]].
let mut this_size = scoped_set.get(agent).get(agent).values.len();
// 6. Let index be 0.
let mut index = 0;
let scoped_has = other_record.has.scope(agent, gc.nogc());
let scoped_set_object = other_record.set_object.scope(agent, gc.nogc());
// 7. Repeat, while index < thisSize,
while index < this_size {
// a. Let entry be set.[[SetData]][index].
let entry = scoped_set.get(agent).get(agent).values[index];
// b. Set index to index + 1.
index += 1;
// c. If entry is not empty, then
if let Some(entry) = entry {
// i. Let inOther be ToBoolean(? Call(otherRecord.[[Has]], otherRecord.[[SetObject]], « entry »)).
let has_result = call_function(
agent,
scoped_has.get(agent),
scoped_set_object.get(agent).into(),
Some(ArgumentsList::from_mut_slice(&mut [entry.unbind()])),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let in_other = to_boolean(agent, has_result);
// ii. If inOther is false, return false.
if !in_other {
return Ok(false.into());
}
// iii. NOTE: The number of elements in set.[[SetData]] may have increased during execution of otherRecord.[[Has]].
// iv. Set thisSize to the number of elements in set.[[SetData]].
this_size = scoped_set.get(agent).get(agent).values.len();
}
}
// 8. Return true.
Ok(true.into())
}

/// ### [24.2.4.14 get Set.prototype.size](https://tc39.es/ecma262/#sec-get-set.prototype.size)
///
/// Set.prototype.size is an accessor property whose set accessor function
Expand Down Expand Up @@ -431,9 +499,10 @@ impl SetPrototype {
let set_prototype_values = intrinsics.set_prototype_values();

OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this)
.with_property_capacity(12)
.with_property_capacity(13)
.with_prototype(object_prototype)
.with_builtin_function_property::<SetPrototypeAdd>()
.with_builtin_function_property::<SetPrototypeIsSubsetOf>()
.with_builtin_function_property::<SetPrototypeClear>()
.with_constructor_property(set_constructor)
.with_builtin_function_property::<SetPrototypeDelete>()
Expand Down
23 changes: 0 additions & 23 deletions tests/expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1932,24 +1932,6 @@
"built-ins/Set/prototype/isDisjointFrom/set-like-iter-return.js": "FAIL",
"built-ins/Set/prototype/isDisjointFrom/size-is-a-number.js": "FAIL",
"built-ins/Set/prototype/isDisjointFrom/subclass-receiver-methods.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/allows-set-like-class.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/allows-set-like-object.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/builtins.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/compares-Map.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/compares-empty-sets.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/compares-itself.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/compares-same-sets.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/compares-sets.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/isSubsetOf.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/length.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/name.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/not-a-constructor.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/require-internal-slot.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/set-like-array.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/set-like-class-mutation.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/set-like-class-order.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/size-is-a-number.js": "FAIL",
"built-ins/Set/prototype/isSubsetOf/subclass-receiver-methods.js": "FAIL",
"built-ins/Set/prototype/isSupersetOf/allows-set-like-class.js": "FAIL",
"built-ins/Set/prototype/isSupersetOf/allows-set-like-object.js": "FAIL",
"built-ins/Set/prototype/isSupersetOf/builtins.js": "FAIL",
Expand Down Expand Up @@ -7421,10 +7403,6 @@
"staging/explicit-resource-management/exception-handling.js": "CRASH",
"staging/explicit-resource-management/mixed-call-dispose-methods.js": "CRASH",
"staging/explicit-resource-management/using-with-null-or-undefined.js": "CRASH",
"staging/set-is-subset-of-empty-index.js": "FAIL",
"staging/set-is-subset-on-set-like.js": "FAIL",
"staging/set-is-subset-table-receiver-cleared.js": "FAIL",
"staging/set-is-subset-table-transition.js": "FAIL",
"staging/set-methods/set-intersect-other-is-set-like.js": "FAIL",
"staging/set-methods/set-intersection-other-is-map.js": "FAIL",
"staging/set-methods/set-intersection-other-is-set.js": "FAIL",
Expand Down Expand Up @@ -7651,7 +7629,6 @@
"staging/sm/Set/difference.js": "FAIL",
"staging/sm/Set/intersection.js": "FAIL",
"staging/sm/Set/is-disjoint-from.js": "FAIL",
"staging/sm/Set/is-subset-of.js": "FAIL",
"staging/sm/Set/is-superset-of.js": "FAIL",
"staging/sm/Set/symmetric-difference.js": "FAIL",
"staging/sm/Set/union.js": "FAIL",
Expand Down
4 changes: 2 additions & 2 deletions tests/metrics.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"results": {
"crash": 162,
"fail": 7572,
"pass": 40692,
"fail": 7549,
"pass": 40715,
"skip": 5127,
"timeout": 22,
"unresolved": 41
Expand Down
Loading