mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 10:16:03 +00:00
feat(codemode): add Set method parity (#38000)
This commit is contained in:
@@ -295,8 +295,8 @@ ultimate source of truth.
|
||||
- [x] Materialized `keys`, `values`, and `entries` arrays for Map and Set.
|
||||
- [x] Spread, `for...of`, `Array.from`, and `Object.fromEntries` integration.
|
||||
- [x] Map and Set values serialize to `{}` at host/JSON boundaries.
|
||||
- [ ] Set composition and relation methods: `union`, `intersection`, `difference`, `symmetricDifference`, `isSubsetOf`,
|
||||
`isSupersetOf`, and `isDisjointFrom`.
|
||||
- [x] Set composition and relation methods: `union`, `intersection`, `difference`, `symmetricDifference`, `isSubsetOf`,
|
||||
`isSupersetOf`, and `isDisjointFrom`, including supported Set-like operands.
|
||||
|
||||
## URL and URI helpers
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export const invokeIntrinsic = <R>(
|
||||
// Native setters read the current time before argument coercion, whose callbacks may mutate the Date.
|
||||
const initialTime = target.time
|
||||
return Effect.map(
|
||||
Effect.forEach(args.slice(0, argumentCount), (arg) => coerceDateSetterArgument(runner, arg, node), {
|
||||
Effect.forEach(args.slice(0, argumentCount), (arg) => coerceNumericArgument(runner, arg, node), {
|
||||
concurrency: 1,
|
||||
}),
|
||||
(values) => invokeDateMethod(target, ref.name, values, node, initialTime),
|
||||
@@ -127,7 +127,7 @@ export const invokeIntrinsic = <R>(
|
||||
throw new InterpreterRuntimeError(`Method '${ref.name}' is not available in CodeMode.`, node)
|
||||
}
|
||||
|
||||
const coerceDateSetterArgument = <R>(
|
||||
const coerceNumericArgument = <R>(
|
||||
runner: CallbackRunner<R>,
|
||||
value: unknown,
|
||||
node: AstNode,
|
||||
@@ -424,7 +424,7 @@ export const invokeGroupBy = <R>(
|
||||
throw new InterpreterRuntimeError(`${namespace}.groupBy expects an iterable collection.`, node).as("TypeError")
|
||||
}
|
||||
const apply = applyCollectionCallback(runner, args[1], `${namespace}.groupBy`, node)
|
||||
const items = groupByItems(source)
|
||||
const items = supportedIterableItems(source)
|
||||
if (items === undefined) {
|
||||
throw new InterpreterRuntimeError(`${namespace}.groupBy expects an iterable collection.`, node).as("TypeError")
|
||||
}
|
||||
@@ -458,7 +458,7 @@ export const invokeGroupBy = <R>(
|
||||
})
|
||||
}
|
||||
|
||||
const groupByItems = (source: unknown): Iterable<unknown> | undefined => {
|
||||
const supportedIterableItems = (source: unknown): Iterable<unknown> | undefined => {
|
||||
if (Array.isArray(source) || typeof source === "string") return source
|
||||
if (source instanceof CodeModeMap) return source.map.entries()
|
||||
if (source instanceof CodeModeSet) return source.set.values()
|
||||
@@ -655,11 +655,140 @@ const invokeSetMethod = <R>(
|
||||
return undefined
|
||||
})
|
||||
}
|
||||
case "union":
|
||||
case "intersection":
|
||||
case "difference":
|
||||
case "symmetricDifference":
|
||||
case "isSubsetOf":
|
||||
case "isSupersetOf":
|
||||
case "isDisjointFrom":
|
||||
return invokeSetOperation(runner, target, name, args[0], node)
|
||||
default:
|
||||
throw new InterpreterRuntimeError(`Set method '${name}' is not available in CodeMode.`, node)
|
||||
}
|
||||
}
|
||||
|
||||
const invokeSetOperation = <R>(
|
||||
runner: CallbackRunner<R>,
|
||||
target: CodeModeSet,
|
||||
name: string,
|
||||
source: unknown,
|
||||
node: AstNode,
|
||||
): Effect.Effect<unknown, unknown, R> =>
|
||||
Effect.gen(function* () {
|
||||
const other = yield* loadSetRecord(runner, source, name, node)
|
||||
if (name === "union") {
|
||||
const result = copySet(target)
|
||||
for (const item of yield* other.keys()) result.set.add(item)
|
||||
return result
|
||||
}
|
||||
if (name === "intersection") {
|
||||
const result = new CodeModeSet()
|
||||
if (target.set.size <= other.size) {
|
||||
for (const item of target.set.values()) {
|
||||
if (yield* other.has(item)) result.set.add(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
for (const item of yield* other.keys()) {
|
||||
if (target.set.has(item)) result.set.add(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (name === "difference") {
|
||||
const result = copySet(target)
|
||||
if (target.set.size <= other.size) {
|
||||
for (const item of result.set.values()) {
|
||||
if (yield* other.has(item)) result.set.delete(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
for (const item of yield* other.keys()) result.set.delete(item)
|
||||
return result
|
||||
}
|
||||
if (name === "symmetricDifference") {
|
||||
const result = copySet(target)
|
||||
for (const item of yield* other.keys()) {
|
||||
if (target.set.has(item)) result.set.delete(item)
|
||||
else result.set.add(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (name === "isSubsetOf") {
|
||||
if (target.set.size > other.size) return false
|
||||
for (const item of target.set.values()) {
|
||||
if (!(yield* other.has(item))) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (name === "isSupersetOf") {
|
||||
if (target.set.size < other.size) return false
|
||||
for (const item of yield* other.keys()) {
|
||||
if (!target.set.has(item)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (target.set.size <= other.size) {
|
||||
for (const item of target.set.values()) {
|
||||
if (yield* other.has(item)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for (const item of yield* other.keys()) {
|
||||
if (target.set.has(item)) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
const copySet = (source: CodeModeSet): CodeModeSet => {
|
||||
const result = new CodeModeSet()
|
||||
for (const item of source.set.values()) result.set.add(item)
|
||||
return result
|
||||
}
|
||||
|
||||
const loadSetRecord = <R>(runner: CallbackRunner<R>, source: unknown, name: string, node: AstNode) => {
|
||||
if (source instanceof CodeModeSet) {
|
||||
return Effect.succeed({
|
||||
size: source.set.size,
|
||||
has: (item: unknown) => Effect.succeed(source.set.has(item)),
|
||||
keys: () => Effect.succeed(source.set.values()),
|
||||
})
|
||||
}
|
||||
if (source instanceof CodeModeMap) {
|
||||
return Effect.succeed({
|
||||
size: source.map.size,
|
||||
has: (item: unknown) => Effect.succeed(source.map.has(item)),
|
||||
keys: () => Effect.succeed(source.map.keys()),
|
||||
})
|
||||
}
|
||||
if (source === null || typeof source !== "object" || isCodeModeValue(source)) {
|
||||
throw new InterpreterRuntimeError(`Set.${name} expects a Set-like object.`, node).as("TypeError")
|
||||
}
|
||||
const object = source as Record<string, unknown>
|
||||
return Effect.gen(function* () {
|
||||
const size = yield* coerceNumericArgument(runner, object.size, node)
|
||||
if (Number.isNaN(size)) {
|
||||
throw new InterpreterRuntimeError(`Set.${name} received a Set-like object with an invalid size.`, node).as(
|
||||
"TypeError",
|
||||
)
|
||||
}
|
||||
if (!isSupportedCallback(object.has) || !isSupportedCallback(object.keys)) {
|
||||
throw new InterpreterRuntimeError(`Set.${name} expects callable 'has' and 'keys' methods.`, node).as("TypeError")
|
||||
}
|
||||
const has = object.has
|
||||
const keys = object.keys
|
||||
return {
|
||||
size: Math.max(Math.trunc(size), 0),
|
||||
has: (item: unknown) => Effect.map(runner.invokeCallable(has, [item], node), Boolean),
|
||||
keys: () =>
|
||||
Effect.flatMap(runner.invokeCallable(keys, [], node), (result) => {
|
||||
if (Array.isArray(result)) return Effect.succeed(result)
|
||||
throw new InterpreterRuntimeError(`Set.${name} expected 'keys' to return an iterator.`, node).as("TypeError")
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const invokeURLSearchParamsMethod = <R>(
|
||||
runner: CallbackRunner<R>,
|
||||
target: CodeModeURLSearchParams,
|
||||
|
||||
@@ -41,7 +41,23 @@ export const mapMethods = new Set(["get", "set", "has", "delete", "clear", "forE
|
||||
|
||||
export const mapStatics = new Set(["groupBy"])
|
||||
|
||||
export const setMethods = new Set(["add", "has", "delete", "clear", "forEach", "keys", "values", "entries"])
|
||||
export const setMethods = new Set([
|
||||
"add",
|
||||
"has",
|
||||
"delete",
|
||||
"clear",
|
||||
"forEach",
|
||||
"keys",
|
||||
"values",
|
||||
"entries",
|
||||
"union",
|
||||
"intersection",
|
||||
"difference",
|
||||
"symmetricDifference",
|
||||
"isSubsetOf",
|
||||
"isSupersetOf",
|
||||
"isDisjointFrom",
|
||||
])
|
||||
|
||||
export const spreadItems = (value: unknown): Array<unknown> | undefined => {
|
||||
if (Array.isArray(value)) return value
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
|
||||
* - test/built-ins/Set/prototype/union/combines-sets.js
|
||||
* - test/built-ins/Set/prototype/union/combines-empty-sets.js
|
||||
* - test/built-ins/Set/prototype/union/combines-itself.js
|
||||
* - test/built-ins/Set/prototype/union/combines-same-sets.js
|
||||
* - test/built-ins/Set/prototype/union/combines-Map.js
|
||||
* - test/built-ins/Set/prototype/union/result-order.js
|
||||
* - test/built-ins/Set/prototype/union/appends-new-values.js
|
||||
* - test/built-ins/Set/prototype/union/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/union/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/union/has-is-callable.js
|
||||
* - test/built-ins/Set/prototype/union/keys-is-callable.js
|
||||
* - test/built-ins/Set/prototype/union/array-throws.js
|
||||
* - test/built-ins/Set/prototype/union/converts-negative-zero.js
|
||||
* - test/built-ins/Set/prototype/union/set-like-class-mutation.js
|
||||
* - test/built-ins/Set/prototype/intersection/combines-sets.js
|
||||
* - test/built-ins/Set/prototype/intersection/combines-itself.js
|
||||
* - test/built-ins/Set/prototype/intersection/result-order.js
|
||||
* - test/built-ins/Set/prototype/intersection/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/intersection/converts-negative-zero.js
|
||||
* - test/built-ins/Set/prototype/intersection/combines-Map.js
|
||||
* - test/built-ins/Set/prototype/intersection/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/intersection/set-like-class-mutation.js
|
||||
* - test/built-ins/Set/prototype/difference/combines-sets.js
|
||||
* - test/built-ins/Set/prototype/difference/combines-itself.js
|
||||
* - test/built-ins/Set/prototype/difference/result-order.js
|
||||
* - test/built-ins/Set/prototype/difference/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/difference/converts-negative-zero.js
|
||||
* - test/built-ins/Set/prototype/difference/combines-Map.js
|
||||
* - test/built-ins/Set/prototype/difference/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/difference/set-like-class-mutation.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/combines-sets.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/combines-itself.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/result-order.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/converts-negative-zero.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/set-like-class-mutation.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/combines-Map.js
|
||||
* - test/built-ins/Set/prototype/symmetricDifference/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/compares-sets.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/compares-Map.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/compares-itself.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/compares-same-sets.js
|
||||
* - test/built-ins/Set/prototype/isSubsetOf/set-like-class-mutation.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/compares-sets.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/compares-Map.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/compares-itself.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/compares-same-sets.js
|
||||
* - test/built-ins/Set/prototype/isSupersetOf/converts-negative-zero.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/compares-sets.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/size-is-a-number.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/compares-Map.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/allows-set-like-object.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/compares-itself.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/compares-same-sets.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/converts-negative-zero.js
|
||||
* - test/built-ins/Set/prototype/isDisjointFrom/set-like-class-mutation.js
|
||||
*
|
||||
* Copyright (C) 2023 Anthony Frehner. All rights reserved.
|
||||
* Copyright (C) 2023 Anthony Frehner and Kevin Gibbons. All rights reserved.
|
||||
* Copyright (C) 2023 Kevin Gibbons. All rights reserved.
|
||||
* Copyright (C) 2023 Kevin Gibbons, Anthony Frehner. All rights reserved.
|
||||
* Test262 portions are governed by the BSD license in LICENSE.test262.
|
||||
* Set-like `keys` methods return arrays instead of iterator objects because
|
||||
* CodeMode materializes supported iterators and does not support generators.
|
||||
*/
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { CodeMode } from "../src/index.js"
|
||||
|
||||
const value = async (code: string) => {
|
||||
const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
|
||||
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
|
||||
return result.value
|
||||
}
|
||||
|
||||
describe("Set composition Test262 parity", () => {
|
||||
test("combines Sets without mutating either operand", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const left = new Set([1, 2])
|
||||
const right = new Set([2, 3])
|
||||
const union = left.union(right)
|
||||
const intersection = left.intersection(right)
|
||||
const difference = left.difference(right)
|
||||
const symmetric = left.symmetricDifference(right)
|
||||
return [
|
||||
[...union], union instanceof Set,
|
||||
[...intersection], intersection instanceof Set,
|
||||
[...difference], difference instanceof Set,
|
||||
[...symmetric], symmetric instanceof Set,
|
||||
[...left], [...right],
|
||||
[...left.union(left)], left.union(left) !== left,
|
||||
[...left.intersection(left)], left.intersection(left) !== left,
|
||||
[...left.difference(left)], left.difference(left) !== left,
|
||||
[...left.symmetricDifference(left)], left.symmetricDifference(left) !== left,
|
||||
[...left.union(new Set())], [...new Set().union(left)],
|
||||
]
|
||||
`),
|
||||
).toEqual([
|
||||
[1, 2, 3],
|
||||
true,
|
||||
[2],
|
||||
true,
|
||||
[1],
|
||||
true,
|
||||
[1, 3],
|
||||
true,
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
[1, 2],
|
||||
true,
|
||||
[1, 2],
|
||||
true,
|
||||
[],
|
||||
true,
|
||||
[],
|
||||
true,
|
||||
[1, 2],
|
||||
[1, 2],
|
||||
])
|
||||
})
|
||||
|
||||
test("preserves the specified result order", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
return [
|
||||
[...new Set([1, 3, 5]).intersection(new Set([3, 2, 1]))],
|
||||
[...new Set([3, 2, 1, 0]).intersection(new Set([1, 3, 5]))],
|
||||
[...new Set([1, 2, 3, 4]).difference(new Set([6, 5, 3, 2]))],
|
||||
[...new Set([6, 5, 3, 2]).difference(new Set([1, 2, 3, 4]))],
|
||||
[...new Set([1, 2, 3, 4]).symmetricDifference(new Set([6, 5, 4, 3]))],
|
||||
[...new Set([2, 3]).union(new Set([1, 2]))],
|
||||
[...new Set([1, 2, -3]).union(new Set([-1, 0]))],
|
||||
]
|
||||
`),
|
||||
).toEqual([
|
||||
[1, 3],
|
||||
[1, 3],
|
||||
[1, 4],
|
||||
[6, 5],
|
||||
[1, 2, 6, 5],
|
||||
[2, 3, 1],
|
||||
[1, 2, -3, -1, 0],
|
||||
])
|
||||
})
|
||||
|
||||
test("accepts Maps as Set-like operands", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const set = new Set([1, 2])
|
||||
const map = new Map([[2, "two"], [3, "three"]])
|
||||
return [
|
||||
[...set.union(map)],
|
||||
[...set.intersection(map)],
|
||||
[...set.difference(map)],
|
||||
[...set.symmetricDifference(map)],
|
||||
]
|
||||
`),
|
||||
).toEqual([[1, 2, 3], [2], [1], [1, 3]])
|
||||
})
|
||||
|
||||
test("accepts supported Set-like objects and uses the size-selected method", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const keysOnly = {
|
||||
size: 1,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => [2, 3],
|
||||
}
|
||||
const hasOnly = {
|
||||
size: 2,
|
||||
has: (value) => value === 2,
|
||||
keys: () => { throw new Error("keys should not be called") },
|
||||
}
|
||||
const set = new Set([1, 2])
|
||||
return [
|
||||
[...set.union(keysOnly)],
|
||||
[...set.intersection(hasOnly)],
|
||||
[...set.difference(hasOnly)],
|
||||
[...set.symmetricDifference(keysOnly)],
|
||||
]
|
||||
`),
|
||||
).toEqual([[1, 2, 3], [2], [1], [1, 3]])
|
||||
})
|
||||
|
||||
test("normalizes negative zero", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const setlike = {
|
||||
size: 1,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => [-0],
|
||||
}
|
||||
return [
|
||||
1 / new Set([1]).union(setlike).values()[1] === Infinity,
|
||||
1 / new Set([0, 1, 2]).intersection(setlike).values()[0] === Infinity,
|
||||
[...new Set([0, 1]).difference(setlike)],
|
||||
1 / new Set([1, 2]).symmetricDifference(setlike).values()[2] === Infinity,
|
||||
]
|
||||
`),
|
||||
).toEqual([true, true, [1], true])
|
||||
})
|
||||
|
||||
test("handles duplicate Set-like keys using receiver membership", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const other = {
|
||||
size: 4,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => [2, 2, 3, 3],
|
||||
}
|
||||
return [...new Set([1, 2]).symmetricDifference(other)]
|
||||
`),
|
||||
).toEqual([1, 3])
|
||||
})
|
||||
|
||||
test("copies receiver data before Set-like key iteration mutates it", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const unionBase = new Set(["a", "b", "c", "d", "e"])
|
||||
const unionOther = {
|
||||
size: 2,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => {
|
||||
unionBase.delete("b")
|
||||
unionBase.delete("c")
|
||||
unionBase.add("b")
|
||||
return ["x", "y"]
|
||||
},
|
||||
}
|
||||
const union = unionBase.union(unionOther)
|
||||
|
||||
const intersectionBase = new Set(["a", "b", "c", "d", "e"])
|
||||
const intersectionOther = {
|
||||
size: 3,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => {
|
||||
intersectionBase.delete("b")
|
||||
intersectionBase.delete("c")
|
||||
intersectionBase.add("b")
|
||||
return ["x", "b", "b"]
|
||||
},
|
||||
}
|
||||
const intersection = intersectionBase.intersection(intersectionOther)
|
||||
|
||||
const differenceBase = new Set(["a", "b", "c", "d", "e"])
|
||||
const differenceOther = {
|
||||
size: 3,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => {
|
||||
differenceBase.delete("b")
|
||||
differenceBase.delete("c")
|
||||
differenceBase.add("b")
|
||||
return ["x", "b", "b"]
|
||||
},
|
||||
}
|
||||
const difference = differenceBase.difference(differenceOther)
|
||||
|
||||
const symmetricBase = new Set(["a", "b", "c", "d", "e"])
|
||||
const symmetricOther = {
|
||||
size: 4,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => {
|
||||
symmetricBase.delete("b")
|
||||
symmetricBase.delete("c")
|
||||
symmetricBase.add("b")
|
||||
return ["x", "b", "c", "c"]
|
||||
},
|
||||
}
|
||||
const symmetric = symmetricBase.symmetricDifference(symmetricOther)
|
||||
|
||||
return [
|
||||
[...union], [...unionBase],
|
||||
[...intersection], [...intersectionBase],
|
||||
[...difference], [...differenceBase],
|
||||
[...symmetric], [...symmetricBase],
|
||||
]
|
||||
`),
|
||||
).toEqual([
|
||||
["a", "b", "c", "d", "e", "x", "y"],
|
||||
["a", "d", "e", "b"],
|
||||
["b"],
|
||||
["a", "d", "e", "b"],
|
||||
["a", "c", "d", "e"],
|
||||
["a", "d", "e", "b"],
|
||||
["a", "c", "d", "e", "x"],
|
||||
["a", "d", "e", "b"],
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Set relation Test262 parity", () => {
|
||||
test("compares Sets", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const set = new Set([1, 2])
|
||||
return [
|
||||
set.isSubsetOf(new Set([2, 3])), set.isSubsetOf(new Set([1, 2, 3])),
|
||||
set.isSupersetOf(new Set([2, 3])), set.isSupersetOf(new Set([1])),
|
||||
set.isDisjointFrom(new Set([2, 3])), set.isDisjointFrom(new Set([3])),
|
||||
new Set().isSubsetOf(set), set.isSupersetOf(new Set()), new Set().isDisjointFrom(new Set()),
|
||||
set.isSubsetOf(set), set.isSupersetOf(set), set.isDisjointFrom(set),
|
||||
set.isSubsetOf(new Set([1, 2])), set.isSupersetOf(new Set([1, 2])),
|
||||
]
|
||||
`),
|
||||
).toEqual([false, true, false, true, false, true, true, true, true, true, true, false, true, true])
|
||||
})
|
||||
|
||||
test("compares Maps and supported Set-like objects", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const set = new Set([1, 2])
|
||||
const map = new Map([[1, "one"], [2, "two"], [3, "three"]])
|
||||
const hasOnly = {
|
||||
size: 2,
|
||||
has: (value) => value === 1 || value === 2,
|
||||
keys: () => { throw new Error("keys should not be called") },
|
||||
}
|
||||
const keysOnly = {
|
||||
size: 1,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => [1],
|
||||
}
|
||||
return [
|
||||
set.isSubsetOf(map), set.isSupersetOf(map), set.isDisjointFrom(map),
|
||||
set.isSubsetOf(hasOnly), set.isSupersetOf(keysOnly), set.isDisjointFrom(hasOnly),
|
||||
]
|
||||
`),
|
||||
).toEqual([true, false, false, true, true, false])
|
||||
})
|
||||
|
||||
test("normalizes negative zero from Set-like keys", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const setlike = {
|
||||
size: 1,
|
||||
has: () => { throw new Error("has should not be called") },
|
||||
keys: () => [-0],
|
||||
}
|
||||
return [
|
||||
new Set([0, 1]).isSupersetOf(setlike),
|
||||
new Set([0, 1]).isDisjointFrom(setlike),
|
||||
]
|
||||
`),
|
||||
).toEqual([true, false])
|
||||
})
|
||||
|
||||
test("observes live receiver mutation while calling Set-like has", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const subset = new Set(["a", "b", "c"])
|
||||
const subsetOther = {
|
||||
size: 3,
|
||||
has: (item) => {
|
||||
if (item === "a") subset.delete("c")
|
||||
return ["x", "a", "b"].includes(item)
|
||||
},
|
||||
keys: () => { throw new Error("keys should not be called") },
|
||||
}
|
||||
const disjoint = new Set(["a", "b", "c"])
|
||||
const disjointOther = {
|
||||
size: 3,
|
||||
has: (item) => {
|
||||
if (item === "a") {
|
||||
disjoint.delete("b")
|
||||
disjoint.delete("c")
|
||||
disjoint.add("b")
|
||||
}
|
||||
if (item === "c") throw new Error("deleted value should not be visited")
|
||||
return false
|
||||
},
|
||||
keys: () => { throw new Error("keys should not be called") },
|
||||
}
|
||||
return [
|
||||
subset.isSubsetOf(subsetOther), [...subset],
|
||||
disjoint.isDisjointFrom(disjointOther), [...disjoint],
|
||||
]
|
||||
`),
|
||||
).toEqual([true, ["a", "b"], true, ["a", "b"]])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Set-like validation Test262 parity", () => {
|
||||
test("rejects arrays, invalid sizes, and non-callable methods with TypeError", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const set = new Set([1])
|
||||
const names = []
|
||||
let coercionCalls = 0
|
||||
const invalid = [
|
||||
[],
|
||||
{ size: undefined, has: () => false, keys: () => [] },
|
||||
{ size: NaN, has: () => false, keys: () => [] },
|
||||
{ size: { valueOf: () => { coercionCalls += 1; return NaN } }, has: () => false, keys: () => [] },
|
||||
{ size: "string", has: () => false, keys: () => [] },
|
||||
{ size: 0, has: undefined, keys: () => [] },
|
||||
{ size: 0, has: {}, keys: () => [] },
|
||||
{ size: 0, has: () => false, keys: undefined },
|
||||
{ size: 0, has: () => false, keys: {} },
|
||||
]
|
||||
for (const other of invalid) {
|
||||
try { set.union(other) } catch (error) { names.push(error.name) }
|
||||
}
|
||||
return [names, coercionCalls]
|
||||
`),
|
||||
).toEqual([
|
||||
[
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
],
|
||||
1,
|
||||
])
|
||||
})
|
||||
|
||||
test("validates Set-like records for every method", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const set = new Set([1])
|
||||
const invalid = { size: NaN, has: () => false, keys: () => [] }
|
||||
const names = []
|
||||
const operations = [
|
||||
() => set.union(invalid),
|
||||
() => set.intersection(invalid),
|
||||
() => set.difference(invalid),
|
||||
() => set.symmetricDifference(invalid),
|
||||
() => set.isSubsetOf(invalid),
|
||||
() => set.isSupersetOf(invalid),
|
||||
() => set.isDisjointFrom(invalid),
|
||||
]
|
||||
for (const operation of operations) {
|
||||
try { operation() } catch (error) { names.push(error.name) }
|
||||
}
|
||||
return names
|
||||
`),
|
||||
).toEqual(["TypeError", "TypeError", "TypeError", "TypeError", "TypeError", "TypeError", "TypeError"])
|
||||
})
|
||||
|
||||
test("requires Set-like keys to return CodeMode's materialized iterator representation", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const set = new Set([1])
|
||||
const names = []
|
||||
for (const keys of [() => "ab", () => new Set([2]), () => new Map([[2, true]])]) {
|
||||
try { set.union({ size: 1, has: () => false, keys }) } catch (error) { names.push(error.name) }
|
||||
}
|
||||
return names
|
||||
`),
|
||||
).toEqual(["TypeError", "TypeError", "TypeError"])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user