feat(codemode): add Set method parity (#38000)
This commit is contained in:
parent
455b5d3165
commit
f5a487ffcd
4 changed files with 615 additions and 7 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue