diff --git a/BUILD.bazel b/BUILD.bazel index 335662ed9c2c..989a939563cc 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -903,6 +903,7 @@ filegroup( "src/builtins/regexp-test.tq", "src/builtins/set-difference.tq", "src/builtins/set-intersection.tq", + "src/builtins/set-is-subset-of.tq", "src/builtins/set-symmetric-difference.tq", "src/builtins/set-union.tq", "src/builtins/string-at.tq", diff --git a/BUILD.gn b/BUILD.gn index d72b0ad12ca5..5fa9aac51138 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1908,6 +1908,7 @@ torque_files = [ "src/builtins/regexp.tq", "src/builtins/set-difference.tq", "src/builtins/set-intersection.tq", + "src/builtins/set-is-subset-of.tq", "src/builtins/set-symmetric-difference.tq", "src/builtins/set-union.tq", "src/builtins/string-at.tq", diff --git a/src/builtins/set-intersection.tq b/src/builtins/set-intersection.tq index fa116c2ddd81..59dc3366b334 100644 --- a/src/builtins/set-intersection.tq +++ b/src/builtins/set-intersection.tq @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include 'src/objects/ordered-hash-table.h' - namespace collections { // https://tc39.es/proposal-set-methods/#sec-set.prototype.intersection diff --git a/src/builtins/set-is-subset-of.tq b/src/builtins/set-is-subset-of.tq new file mode 100644 index 000000000000..5b6ea9ec4d53 --- /dev/null +++ b/src/builtins/set-is-subset-of.tq @@ -0,0 +1,65 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +namespace collections { + +// https://tc39.es/proposal-set-methods/#sec-set.prototype.issubsetof +transitioning javascript builtin SetPrototypeIsSubsetOf( + js-implicit context: NativeContext, + receiver: JSAny)(other: JSAny): Boolean { + const methodName: constexpr string = 'Set.prototype.isSubsetOf'; + + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[SetData]]). + const o = Cast(receiver) otherwise + ThrowTypeError( + MessageTemplate::kIncompatibleMethodReceiver, methodName, receiver); + + const table = Cast(o.table) otherwise unreachable; + + // 3. Let otherRec be ? GetSetRecord(other). + let otherRec = GetSetRecord(other, methodName); + + // 4. Let thisSize be the number of elements in O.[[SetData]]. + const thisSize = + LoadOrderedHashTableMetadata(table, kOrderedHashSetNumberOfElementsIndex); + + // 5. If thisSize > otherRec.[[Size]], return false. + if (thisSize > Convert(otherRec.size)) { + return False; + } + + // 6. Let index be 0. + let thisIter = collections::NewOrderedHashSetIterator(table); + let key: JSAny; + + // 7. Repeat, while index < thisSize, + while (true) { + try { + // a. Let e be O.[[SetData]][index]. + key = thisIter.Next() otherwise Done; + } label Done { + // 8. Return true. + return True; + } + + // b. Set index to index + 1. + // c. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[Set]], « + // e »)). + const inOther = + ToBoolean(Call(context, otherRec.has, otherRec.object, key)); + + // d. If inOther is false, return false. + if (!inOther) { + return False; + } + // e. NOTE: The number of elements in O.[[SetData]] may have increased + // during execution of otherRec.[[Has]]. + // f. Set thisSize to the number of elements of O.[[SetData]]. + // We have used `collections::NewOrderedHashSetIterator` which allows + // changes on the table. + } + unreachable; +} +} diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index 67d31470b137..cd7663b86679 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -4727,6 +4727,8 @@ void Genesis::InitializeGlobal_harmony_set_methods() { Builtin::kSetPrototypeDifference, 1, true); SimpleInstallFunction(isolate(), set_prototype, "symmetricDifference", Builtin::kSetPrototypeSymmetricDifference, 1, true); + SimpleInstallFunction(isolate(), set_prototype, "isSubsetOf", + Builtin::kSetPrototypeIsSubsetOf, 1, true); } void Genesis::InitializeGlobal_harmony_json_parse_with_source() { diff --git a/test/mjsunit/harmony/set-is-subset-of.js b/test/mjsunit/harmony/set-is-subset-of.js new file mode 100644 index 000000000000..94db898703d2 --- /dev/null +++ b/test/mjsunit/harmony/set-is-subset-of.js @@ -0,0 +1,181 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Flags: --harmony-set-methods + +(function TestIsSubsetOfSetFirstShorterIsSubset() { + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + + const otherSet = new Set(); + otherSet.add(42); + otherSet.add(43); + otherSet.add(47); + + assertEquals(firstSet.isSubsetOf(otherSet), true); +})(); + +(function TestIsSubsetOfSetFirstShorterIsNotSubset() { + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + + const otherSet = new Set(); + otherSet.add(42); + otherSet.add(46); + otherSet.add(47); + + assertEquals(firstSet.isSubsetOf(otherSet), false); +})(); + +(function TestIsSubsetOfSetSecondShorter() { + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + firstSet.add(44); + + const otherSet = new Set(); + otherSet.add(42); + otherSet.add(44); + + assertEquals(firstSet.isSubsetOf(otherSet), false); +})(); + +(function TestIsSubsetOfMapFirstShorterIsSubset() { + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + + const other = new Map(); + other.set(42); + other.set(43); + other.set(47); + + assertEquals(firstSet.isSubsetOf(other), true); +})(); + +(function TestIsSubsetOfMapFirstShorterIsNotSubset() { + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + + const other = new Map(); + other.set(42); + other.set(46); + other.set(47); + + assertEquals(firstSet.isSubsetOf(other), false); +})(); + +(function TestIsSubsetOfMapSecondShorter() { + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + firstSet.add(44); + + const other = new Map(); + other.set(42); + other.set(43); + + assertEquals(firstSet.isSubsetOf(other), false); +})(); + +(function TestIsSubsetOfSetLikeObjectFirstShorterIsSubset() { + const SetLike = { + arr: [42, 44, 45], + size: 3, + keys() { + return this.arr[Symbol.iterator](); + }, + has(key) { + return this.arr.indexOf(key) != -1; + } + }; + + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(45); + + assertEquals(firstSet.isSubsetOf(SetLike), true); +})(); + +(function TestIsSubsetOfSetLikeObjectFirstShorterIsNotSubset() { + const SetLike = { + arr: [42, 44, 45], + size: 3, + keys() { + return this.arr[Symbol.iterator](); + }, + has(key) { + return this.arr.indexOf(key) != -1; + } + }; + + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + + assertEquals(firstSet.isSubsetOf(SetLike), false); +})(); + +(function TestIsSubsetOfSetLikeObjectSecondShorter() { + const SetLike = { + arr: [42, 43], + size: 3, + keys() { + return this.arr[Symbol.iterator](); + }, + has(key) { + return this.arr.indexOf(key) != -1; + } + }; + + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + firstSet.add(44); + + assertEquals(firstSet.isSubsetOf(SetLike), false); +})(); + +(function TestIsSubsetOfSetEqualLengthIsSubset() { + const SetLike = { + arr: [42, 43, 45], + size: 3, + keys() { + return this.arr[Symbol.iterator](); + }, + has(key) { + return this.arr.indexOf(key) != -1; + } + }; + + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + firstSet.add(45); + + assertEquals(firstSet.isSubsetOf(SetLike), true); +})(); + +(function TestIsSubsetOfSetEqualLengthIsNotSubset() { + const SetLike = { + arr: [42, 44, 45], + size: 3, + keys() { + return this.arr[Symbol.iterator](); + }, + has(key) { + return this.arr.indexOf(key) != -1; + } + }; + + const firstSet = new Set(); + firstSet.add(42); + firstSet.add(43); + firstSet.add(45); + + assertEquals(firstSet.isSubsetOf(SetLike), false); +})();