mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-24 17:47:34 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a04ed7f33 |
@@ -139,11 +139,24 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
arrow function.
|
||||
- [x] Promise-returning string replacers are coerced synchronously to `"[object Promise]"`, like JavaScript; they are
|
||||
not automatically awaited.
|
||||
- [x] The optional `thisArg` of iteration methods is accepted and ignored: CodeMode functions have no `this`, so
|
||||
ignoring it matches JS arrow-function semantics exactly.
|
||||
- [ ] `this` in non-arrow CodeMode functions and callbacks.
|
||||
- [x] `this` in non-arrow functions is the call's receiver: `obj.m()` and `obj["m"]()` see `obj`, a bare or detached
|
||||
call (`f()`, `const m = obj.m; m()`, `(0, obj.m)()`) sees `undefined`, as in strict JS. Arrows read the enclosing
|
||||
function's `this`. Program code has no receiver, so top-level `this` is `undefined`, as in a module.
|
||||
- [x] `arguments` in non-arrow functions: an unmapped ordinary object with the call's arguments as indexed
|
||||
properties and a hidden `length`; iterable, so spread, `for...of`, and `Array.from` work. It is not an Array
|
||||
(`JSON.stringify` gives `{"0":1}`, `String` gives `[object Arguments]`). A parameter named `arguments` shadows
|
||||
it; arrows read the enclosing function's; it is only created for functions whose body mentions it. `callee`
|
||||
and `caller` are absent rather than poisoned.
|
||||
- [ ] Array methods on `arguments` and other array-likes (`Array.prototype.slice.call(arguments, 1)`); use
|
||||
`[...arguments]` or a rest parameter meanwhile.
|
||||
- [x] `Function.prototype.call`, `apply`, and `bind` on program functions and built-ins:
|
||||
`Array.prototype.push.call(arr, 1)`, `Math.max.apply(null, values)`, `fn.bind(obj, first)`. `apply` accepts an
|
||||
array, an array-like object, or `null`/`undefined`. A bound function is named `bound f`, has its remaining
|
||||
`length`, and is not constructible.
|
||||
- [x] `JSON.parse` revivers and `JSON.stringify` function replacers see the holder object as `this`.
|
||||
- [ ] The optional `thisArg` of iteration methods (`map`, `forEach`, `Map.prototype.forEach`, `Array.from`, …) is
|
||||
accepted but not yet passed as `this`; callbacks run with `this` undefined.
|
||||
- [ ] User-defined constructor calls.
|
||||
- [ ] `Function.prototype.call`, `apply`, and `bind` for CodeMode functions.
|
||||
- [ ] Classes and private fields.
|
||||
- [x] Functions are objects: they hold own properties (`fn.count = 1`), enumerate them, and expose read-only `name`
|
||||
and `length`. Names follow JavaScript's NamedEvaluation: declarations, named expressions, bindings,
|
||||
@@ -178,7 +191,7 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
yielded promises; mixed async request queues; sync and async `yield*` forwarding; malformed methods/results;
|
||||
and declaration, expression, and object-method forms with closure and parameter behavior. The adapted suite
|
||||
deliberately skips Test262 variants whose observation mechanism requires unsupported getter definitions,
|
||||
proxies, prototype inspection or mutation, non-arrow `this`, classes, or arbitrary symbols. It also skips tests
|
||||
proxies, prototype inspection or mutation, classes, or arbitrary symbols. It also skips tests
|
||||
asserting exact promise reaction-turn counts beyond the observable ordering guarantee documented below. These
|
||||
are interpreter-surface boundaries, not claims that the corresponding full Test262 families pass unchanged.
|
||||
|
||||
@@ -269,7 +282,7 @@ reject }` object.
|
||||
- [x] Recursive assimilation of objects with an own callable `then` field across `Promise.resolve`, combinators,
|
||||
constructors, reactions, `finally`, `await`, and async returns. Thenable methods run deferred, receive
|
||||
first-call-wins resolve/reject functions, and ignore throws after settlement. Inherited/accessor `then` fields
|
||||
and a JavaScript `this` receiver remain outside the supported object/function model.
|
||||
remain outside the supported object model.
|
||||
- [x] Dotted tool names are canonicalized into namespace paths; a path can be both callable and a namespace, and the
|
||||
last tool supplied for a canonical path wins.
|
||||
- [x] Tool path segments may be named `constructor`, `prototype`, or `__proto__` because paths use inert Map keys.
|
||||
@@ -419,9 +432,9 @@ reject }` object.
|
||||
- [x] `JSON.parse` and `JSON.stringify` for supported data objects.
|
||||
- [x] Numeric/string indentation for `JSON.stringify`.
|
||||
- [x] `JSON.parse` reviver callbacks, including postorder traversal, deletion through `undefined`, and root replacement.
|
||||
Revivers receive `(key, value)` but no `this` holder because CodeMode functions intentionally have no `this`.
|
||||
Revivers receive `(key, value)` with the holder as `this`.
|
||||
- [x] `JSON.stringify` function and array replacers. Function replacers receive `(key, value)` in preorder, including
|
||||
the root, but no `this` holder. Array replacers preserve requested property order, deduplicate names, coerce
|
||||
the root, with the holder as `this`. Array replacers preserve requested property order, deduplicate names, coerce
|
||||
number primitives, and ignore non-string/non-number entries. Primitive wrapper entries remain unsupported.
|
||||
- [x] Captured `console.log`, `console.info`, `console.debug`, `console.warn`, and `console.error`. An Error prints as
|
||||
`Error.prototype.toString` would show it (`Error: boom`), wherever it appears in the logged value.
|
||||
|
||||
@@ -24,7 +24,7 @@ import { typeofValue } from "./interpreter/references.js"
|
||||
|
||||
export type Json = Schema.Json
|
||||
|
||||
type Replacer<R> = (args: Array<Value>) => Effect.Effect<Value, unknown, R>
|
||||
type Replacer<R> = (args: Array<Value>, holder: Obj) => Effect.Effect<Value, unknown, R>
|
||||
|
||||
/**
|
||||
* What `JSON.stringify` would serialize for a program value, as host JSON: `toJSON` is honored, functions and
|
||||
@@ -60,7 +60,7 @@ const walk = <R>(
|
||||
const settled = raw instanceof PromiseObj ? yield* ctx.await(raw) : raw
|
||||
const toJSON = settled instanceof Obj ? get(settled, "toJSON") : undefined
|
||||
const own = toJSON instanceof Callable ? yield* ctx.call(toJSON, settled, [key]) : settled
|
||||
const value = replacer === undefined ? own : yield* replacer([key, own])
|
||||
const value = replacer === undefined ? own : yield* replacer([key, own], holder)
|
||||
if (value === undefined || typeofValue(value) === "function") return undefined
|
||||
if (typeof value === "number") return Number.isFinite(value) ? value : null
|
||||
if (value === null || typeof value === "string" || typeof value === "boolean") return value
|
||||
|
||||
@@ -78,7 +78,7 @@ export const applyCollectionCallback = <R>(
|
||||
ctx: Interpreter<R>,
|
||||
callback: Value,
|
||||
name: string,
|
||||
): ((args: Array<Value>) => Effect.Effect<Value, unknown, R>) => {
|
||||
): ((args: Array<Value>, thisValue?: Value) => Effect.Effect<Value, unknown, R>) => {
|
||||
if (!isSupportedCallback(callback)) {
|
||||
if (typeofValue(callback) === "function") {
|
||||
throw typeError(
|
||||
@@ -87,5 +87,5 @@ export const applyCollectionCallback = <R>(
|
||||
}
|
||||
throw typeError(`${name} expects a function callback.`)
|
||||
}
|
||||
return (callbackArgs) => ctx.call(callback, undefined, callbackArgs)
|
||||
return (callbackArgs, thisValue) => ctx.call(callback, thisValue, callbackArgs)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Effect } from "effect"
|
||||
import type { Value } from "./objects.js"
|
||||
import { Arr, Callable, coerceToInteger, coerceToString, get, Obj, type Value } from "./objects.js"
|
||||
import { arrayGlobal } from "../stdlib/array.js"
|
||||
import { textDecoderGlobal, textEncoderGlobal, uint8ArrayGlobal } from "../stdlib/bytes.js"
|
||||
import { mapGlobal, setGlobal } from "../stdlib/collections.js"
|
||||
@@ -19,7 +19,7 @@ import { base64Global, cryptoGlobal, structuredCloneGlobal } from "../stdlib/web
|
||||
import { ToolReference } from "../tool-runtime.js"
|
||||
import { errorGlobal } from "./errors.js"
|
||||
import { errorTypes } from "./intrinsics.js"
|
||||
import { constants, constructor, native } from "./native.js"
|
||||
import { constants, constructor, methods, native, receiver } from "./native.js"
|
||||
import { AsyncIteratorSymbol, IteratorSymbol, typeError } from "./model.js"
|
||||
import { generatorGlobals } from "./generators.js"
|
||||
import { promiseGlobal } from "./promises.js"
|
||||
@@ -31,6 +31,24 @@ const functionGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
Effect.sync(() => {
|
||||
throw typeError("The Function constructor is not supported; write the function inline.")
|
||||
})
|
||||
const target = (thisValue: Value, method: string) => receiver(Callable, thisValue, `Function.prototype.${method}`)
|
||||
methods(ctx.builtins, ctx.builtins.Function, [
|
||||
["call", 1, (thisValue, args) => ctx.call(target(thisValue, "call"), args[0], args.slice(1))],
|
||||
["apply", 2, (thisValue, args) => ctx.call(target(thisValue, "apply"), args[0], listFromArrayLike(args[1]))],
|
||||
[
|
||||
"bind",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const fn = target(thisValue, "bind")
|
||||
const bound = args.slice(1)
|
||||
return native<R>(ctx.builtins, {
|
||||
name: `bound ${coerceToString(get(fn, "name"))}`,
|
||||
length: Math.max(0, fn.length - bound.length),
|
||||
call: (_, rest) => ctx.call(fn, args[0], [...bound, ...rest]),
|
||||
})
|
||||
},
|
||||
],
|
||||
])
|
||||
return constructor<R>(ctx.builtins, ctx.builtins.Function, {
|
||||
name: "Function",
|
||||
length: 1,
|
||||
@@ -39,6 +57,14 @@ const functionGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
})
|
||||
}
|
||||
|
||||
// CreateListFromArrayLike: `apply` reads `length` and the indexed properties of any object.
|
||||
const listFromArrayLike = (value: Value): Array<Value> => {
|
||||
if (value === undefined || value === null) return []
|
||||
if (value instanceof Arr) return [...value.items]
|
||||
if (!(value instanceof Obj)) throw typeError("Function.prototype.apply expects an array-like argument list.")
|
||||
return Array.from({ length: coerceToInteger(get(value, "length")) }, (_, index) => get(value, String(index)))
|
||||
}
|
||||
|
||||
const symbolGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
const symbol = native<R>(ctx.builtins, {
|
||||
name: "Symbol",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
AnyNode,
|
||||
ArrayExpression,
|
||||
ArrayPattern,
|
||||
AssignmentPattern,
|
||||
@@ -81,6 +82,7 @@ import {
|
||||
keys,
|
||||
Native,
|
||||
parseArrayIndex,
|
||||
Arguments,
|
||||
Arr,
|
||||
Fn,
|
||||
GeneratorObj,
|
||||
@@ -178,6 +180,24 @@ const collectPatternNames = (pattern: Pattern, out: Array<string> = []): Array<s
|
||||
return out
|
||||
}
|
||||
|
||||
// Whether a function body (or a parameter default) reads `arguments`, looking through arrows but not nested
|
||||
// functions, which own theirs. Memoized so the object is only built for calls that can observe it.
|
||||
const argumentsUse = new WeakMap<Fn["body"], boolean>()
|
||||
const usesArguments = (fn: Fn): boolean => {
|
||||
const cached = argumentsUse.get(fn.body)
|
||||
if (cached !== undefined) return cached
|
||||
const found = [...fn.parameters, fn.body].some(function visit(node: AnyNode | null): boolean {
|
||||
if (node === null || typeof node !== "object") return false
|
||||
if (node.type === "Identifier") return node.name === "arguments"
|
||||
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") return false
|
||||
return Object.values(node).some((child) =>
|
||||
Array.isArray(child) ? child.some((item) => visit(item)) : visit(child as AnyNode | null),
|
||||
)
|
||||
})
|
||||
argumentsUse.set(fn.body, found)
|
||||
return found
|
||||
}
|
||||
|
||||
// `var` names declared anywhere in a function body except inside nested functions, which own theirs.
|
||||
// Memoized per body: a function's var names never change, and hoisting runs on every call.
|
||||
const varNames = new WeakMap<ReadonlyArray<Statement | ModuleDeclaration>, ReadonlyArray<string>>()
|
||||
@@ -287,7 +307,8 @@ export class Interpreter<R> {
|
||||
this.pending = options.pending
|
||||
this.builtins = options.builtins
|
||||
this.logs = options.logs ?? []
|
||||
const globalScope = new Map<string, Binding>()
|
||||
// Program code has no receiver: top-level `this` is undefined, as in a module.
|
||||
const globalScope = new Map<string, Binding>([["this", { mutable: false, value: undefined }]])
|
||||
// Calling back into the program never reads frame state, so any frame serves; the root is always alive.
|
||||
this.root = new Frame(this, new ScopeStack([globalScope]))
|
||||
for (const [name, value] of [...globals(this), ...(options.globals?.(this) ?? [])]) {
|
||||
@@ -468,6 +489,7 @@ class Frame<R> {
|
||||
this.scopes.capture(),
|
||||
node.async,
|
||||
node.generator,
|
||||
node.type === "ArrowFunctionExpression",
|
||||
)
|
||||
// Each generator function gets its own prototype, so `g() instanceof g` holds as in JS.
|
||||
if (node.generator)
|
||||
@@ -1257,6 +1279,8 @@ class Frame<R> {
|
||||
}
|
||||
case "Identifier":
|
||||
return Effect.sync(() => this.scopes.get(node.name, node))
|
||||
case "ThisExpression":
|
||||
return Effect.sync(() => this.scopes.get("this", node))
|
||||
case "BinaryExpression":
|
||||
return this.evaluateBinaryExpression(node)
|
||||
case "LogicalExpression":
|
||||
@@ -1622,7 +1646,7 @@ class Frame<R> {
|
||||
}
|
||||
return yield* self.createToolCallPromise(callable.path, args)
|
||||
}
|
||||
if (callable instanceof Fn) return yield* self.invokeFunction(callable, args, node)
|
||||
if (callable instanceof Fn) return yield* self.invokeFunction(callable, thisValue, args, node)
|
||||
if (callable instanceof Native) {
|
||||
return yield* self.native(() => (callable as Native<R>).call(thisValue, args), node)
|
||||
}
|
||||
@@ -1664,14 +1688,24 @@ class Frame<R> {
|
||||
}
|
||||
|
||||
// A callback invoked by a built-in runs below the call that invoked the built-in, so the deeper of the two counts.
|
||||
invokeFunction(fn: Fn, args: Array<Value>, node?: AstNode): Effect.Effect<Value, unknown, R> {
|
||||
invokeFunction(fn: Fn, thisValue: Value, args: Array<Value>, node?: AstNode): Effect.Effect<Value, unknown, R> {
|
||||
const self = this
|
||||
return Effect.flatMap(CallSite, (site) => {
|
||||
const depth = Math.max(self.depth, site.depth) + 1
|
||||
if (depth > MAX_CALL_DEPTH) throw rangeError("Maximum call stack size exceeded", node)
|
||||
const invocation = new Frame(this.ctx, new ScopeStack([...fn.capturedScopes, new Map()]), depth)
|
||||
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
||||
const paramScope = invocation.scopes.current()
|
||||
// `this` and `arguments` are scope bindings so arrows resolve them lexically; a parameter named
|
||||
// `arguments` shadows the object, as in JS.
|
||||
if (!fn.arrow) paramScope.set("this", { mutable: false, value: thisValue, initialized: true })
|
||||
if (!fn.arrow && usesArguments(fn)) {
|
||||
paramScope.set("arguments", {
|
||||
mutable: true,
|
||||
value: new Arguments(self.ctx.builtins.Object, args),
|
||||
initialized: true,
|
||||
})
|
||||
}
|
||||
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
||||
for (const parameter of fn.parameters) {
|
||||
for (const name of collectPatternNames(parameter)) {
|
||||
paramScope.set(name, { mutable: true, value: undefined, initialized: false })
|
||||
|
||||
@@ -72,7 +72,7 @@ export const uriError = failure("URIError")
|
||||
|
||||
// Orient the agent rather than enumerate JavaScript; interpreter-support.md is the full matrix.
|
||||
export const supportedSyntaxMessage =
|
||||
"This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead."
|
||||
"This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead."
|
||||
|
||||
export const unsupportedSyntax = (kind: string, node: AstNode): PendingThrow =>
|
||||
new PendingThrow(
|
||||
|
||||
@@ -149,7 +149,11 @@ export abstract class Opaque extends Obj {
|
||||
|
||||
export abstract class Callable extends Opaque {
|
||||
override readonly tag = "Function"
|
||||
constructor(proto: Obj, name: string, length: number) {
|
||||
constructor(
|
||||
proto: Obj,
|
||||
name: string,
|
||||
readonly length: number,
|
||||
) {
|
||||
super(proto)
|
||||
define(this, "length", length, readonly)
|
||||
define(this, "name", name, readonly)
|
||||
@@ -168,12 +172,29 @@ export class Fn extends Callable {
|
||||
readonly capturedScopes: Array<Map<string, Binding>>,
|
||||
readonly async: boolean,
|
||||
readonly generator: boolean,
|
||||
/** Arrows have no `this` or `arguments` of their own; they read the enclosing function's. */
|
||||
readonly arrow: boolean,
|
||||
) {
|
||||
const optional = parameters.findIndex((p) => p.type === "AssignmentPattern" || p.type === "RestElement")
|
||||
super(proto, name, optional === -1 ? parameters.length : optional)
|
||||
}
|
||||
}
|
||||
|
||||
/** The strict `arguments` object: an ordinary object with indexed own properties and a hidden `length`. */
|
||||
export class Arguments extends Obj {
|
||||
override readonly tag = "Arguments"
|
||||
constructor(proto: Obj, args: Array<Value>) {
|
||||
super(proto)
|
||||
args.forEach((arg, index) => define(this, String(index), arg))
|
||||
define(this, "length", args.length, hidden)
|
||||
}
|
||||
override iterator() {
|
||||
return keys(this)
|
||||
.map((key) => get(this, key))
|
||||
.values()
|
||||
}
|
||||
}
|
||||
|
||||
export type NativeCall<R> = (thisValue: Value, args: Array<Value>) => Effect.Effect<Value, unknown, R>
|
||||
export type NativeConstruct<R> = (args: Array<Value>, newTarget: Callable) => Effect.Effect<Value, unknown, R>
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const parse = <R>(ctx: Interpreter<R>, args: Array<Value>): Effect.Effect<Value,
|
||||
else set(value, name, revived)
|
||||
}
|
||||
}
|
||||
return yield* apply([key, value])
|
||||
return yield* apply([key, value], holder)
|
||||
})
|
||||
return visit(record(ctx.builtins.Object, { "": parsed }), "")
|
||||
}
|
||||
|
||||
@@ -210,11 +210,14 @@ describe("Test262 JSON.stringify replacer adaptations", () => {
|
||||
})
|
||||
|
||||
describe("CodeMode JSON callback boundaries", () => {
|
||||
test("this remains unsupported rather than exposing callback holders", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
CodeMode.execute({ code: `return JSON.parse("1", function (key, item) { return this })`, tools: {} }),
|
||||
)
|
||||
expect(result).toMatchObject({ ok: false, error: { kind: "UnsupportedSyntax" } })
|
||||
test("revivers and replacers see the holder as this", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const revived = JSON.parse('{"a":{"b":1}}', function (key, item) { return key === "b" ? this.b + 1 : item })
|
||||
const text = JSON.stringify({ a: 1, b: 2 }, function (key, item) { return key === "a" ? this.b : item })
|
||||
return [revived, text]
|
||||
`),
|
||||
).toEqual([{ a: { b: 2 } }, '{"a":2,"b":2}'])
|
||||
})
|
||||
|
||||
test("prototype-named keys parse as own data and reach the reviver", async () => {
|
||||
|
||||
@@ -77,6 +77,6 @@ describe("new on a non-constructible callee", () => {
|
||||
expect(failure.message).toStartWith(
|
||||
"SyntaxError: Syntax 'ClassDeclaration' is not supported. This is a restricted JavaScript-like language. Supported: ",
|
||||
)
|
||||
expect(failure.message).toContain("Unsupported: classes, this, getters/setters, BigInt, and custom Symbols.")
|
||||
expect(failure.message).toContain("Unsupported: classes, getters/setters, BigInt, and custom Symbols.")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1461,3 +1461,91 @@ describe("structuredClone", () => {
|
||||
expect((await error(`structuredClone()`)).message).toContain("structuredClone requires 1 argument")
|
||||
})
|
||||
})
|
||||
|
||||
describe("this, arguments, and Function.prototype.call/apply/bind", () => {
|
||||
test("this is the call receiver for non-arrow functions and lexical for arrows", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const o = { n: 1, m() { return this.n }, a() { return (() => this.n)() }, bare() { return this } }
|
||||
const detached = o.bare
|
||||
function f() { return this }
|
||||
return [o.m(), o["m"](), o?.m(), (o.m)(), o.a(), o.bare() === o, detached(), f(), (0, o.bare)(), this, (() => this)()]
|
||||
`),
|
||||
).toEqual([1, 1, 1, 1, 1, true, null, null, null, null, null])
|
||||
})
|
||||
|
||||
test("this reaches generator and async methods and plain-function callbacks", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const o = { n: 2, *g() { yield this.n }, async m() { return this.n }, xs: [1, 2], go() { return this.xs.map(function (x) { return [x, this] }) } }
|
||||
return [[...o.g()], await o.m(), o.go()]
|
||||
`),
|
||||
).toEqual([
|
||||
[2],
|
||||
2,
|
||||
[
|
||||
[1, null],
|
||||
[2, null],
|
||||
],
|
||||
])
|
||||
})
|
||||
|
||||
test("arguments is an unmapped array-like that arrows and parameters interact with as in JS", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
function f(a) { arguments[0] = 9; return [arguments.length, arguments[1], a, [...arguments], Array.isArray(arguments), JSON.stringify(arguments), typeof arguments.map, (() => arguments[1])()] }
|
||||
function shadow(arguments) { return arguments }
|
||||
function hoisted() { var arguments; return arguments.length }
|
||||
let outer
|
||||
try { outer = arguments } catch (error) { outer = error.name }
|
||||
return [f(1, 2), shadow(7), hoisted(1, 2, 3), outer]
|
||||
`),
|
||||
).toEqual([[2, 2, 1, [9, 2], false, '{"0":9,"1":2}', "undefined", 2], 7, 3, "ReferenceError"])
|
||||
})
|
||||
|
||||
test("call, apply, and bind set this and arguments on program functions and built-ins", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
function f(a, b, c) { return [this, a, b, c] }
|
||||
const g = f.bind({ k: 1 }, "A")
|
||||
const arr = [1]
|
||||
Array.prototype.push.call(arr, 2, 3)
|
||||
return [
|
||||
f.call("t", 1, 2),
|
||||
f.apply({ k: 2 }, [1, 2]),
|
||||
f.apply(null, { length: 2, 0: "x", 1: "y" }),
|
||||
f.apply(null).length,
|
||||
g("B", "C"), g.name, g.length,
|
||||
f.bind(1).bind(2)()[0],
|
||||
arr,
|
||||
Math.max.apply(null, [1, 5, 3]),
|
||||
Math.max.bind(null, 10)(3),
|
||||
[1, 2].map(f.bind(null, 0)).map((r) => r[1]),
|
||||
]
|
||||
`),
|
||||
).toEqual([
|
||||
["t", 1, 2, null],
|
||||
[{ k: 2 }, 1, 2, null],
|
||||
[null, "x", "y", null],
|
||||
4,
|
||||
[{ k: 1 }, "A", "B", "C"],
|
||||
"bound f",
|
||||
2,
|
||||
1,
|
||||
[1, 2, 3],
|
||||
5,
|
||||
10,
|
||||
[0, 0],
|
||||
])
|
||||
})
|
||||
|
||||
test("call and apply reject non-callable receivers and non-array-like argument lists", async () => {
|
||||
expect((await error(`Function.prototype.call.call(1)`)).message).toContain(
|
||||
"Function.prototype.call called on incompatible receiver",
|
||||
)
|
||||
expect((await error(`(() => 1).apply(null, 5)`)).message).toContain("expects an array-like argument list")
|
||||
expect((await error(`function f(n) { return f.call(null, n + 1) } f(0)`)).message).toContain(
|
||||
"Maximum call stack size exceeded",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1416,7 +1416,9 @@ describe("built-in iterators", () => {
|
||||
const logged = await run(`console.log([1].keys()); return null`)
|
||||
expect(logged.logs?.[0]).toBe("[opaque reference]")
|
||||
expect((await error(`return [1].keys() + ""`)).message).toContain("Binary operators require data values")
|
||||
expect((await error(`return [1].keys().next.call({})`)).message).toContain("is not a function")
|
||||
expect((await error(`return [1].keys().next.call({})`)).message).toContain(
|
||||
"Iterator.prototype.next called on incompatible receiver a data object",
|
||||
)
|
||||
expect((await error(`const it = [1].keys(); const next = it.next; return next()`)).message).toContain(
|
||||
"Iterator.prototype.next called on incompatible receiver undefined",
|
||||
)
|
||||
|
||||
@@ -24,9 +24,9 @@ Without them the runner registers no tests, so CI is unaffected. Licensed under
|
||||
|
||||
`script/sync-test262.ts` skips a file when its frontmatter declares a `flags`, `features`, or `includes` value the
|
||||
manifest marks unsupported, or when its code matches one of the manifest's `boundaries` patterns. The sync checks the
|
||||
checkout is at the pinned revision, so every machine runs the same 4595 files. Boundaries are
|
||||
intentional limits of the interpreter, not compatibility work: classes, `this`, `arguments`, prototype objects,
|
||||
property descriptors, accessors, boxed primitives, sloppy mode, `eval`, `Symbol()`, and the `$262` host API. If one
|
||||
checkout is at the pinned revision, so every machine runs the same files. Boundaries are
|
||||
intentional limits of the interpreter, not compatibility work: classes, prototype objects, property descriptors,
|
||||
accessors, boxed primitives, sloppy mode, `eval`, `Symbol()`, and the `$262` host API. If one
|
||||
of those decisions changes, delete its entry and re-sync; the tests are upstream, not lost.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
"revision": "250f204f23a9249ff204be2baec29600faae7b75",
|
||||
"directories": [
|
||||
"built-ins/Array/prototype",
|
||||
"built-ins/Function/prototype/apply",
|
||||
"built-ins/Function/prototype/bind",
|
||||
"built-ins/Function/prototype/call",
|
||||
"built-ins/Iterator",
|
||||
"built-ins/Object/freeze",
|
||||
"built-ins/Object/getPrototypeOf",
|
||||
@@ -11,18 +14,17 @@
|
||||
"built-ins/Object/isSealed",
|
||||
"built-ins/Object/preventExtensions",
|
||||
"built-ins/String/raw",
|
||||
"language/arguments-object",
|
||||
"language/expressions/does-not-equals",
|
||||
"language/expressions/equals",
|
||||
"language/expressions/tagged-template",
|
||||
"language/expressions/this",
|
||||
"language/statements"
|
||||
],
|
||||
"harness": ["assert.js", "sta.js", "compareArray.js", "doneprintHandle.js"],
|
||||
"flags": ["module", "raw", "noStrict"],
|
||||
"boundaries": {
|
||||
"class": "\\bclass\\s*[A-Za-z_${]",
|
||||
"this": "\\bthis\\b",
|
||||
"arguments": "\\barguments\\b",
|
||||
"call/apply/bind": "\\.(call|apply|bind)\\s*\\(",
|
||||
"accessor properties": "\\b(get|set)\\s+[\\w$\\[][^\\n(]*\\(",
|
||||
"property descriptors": "Object\\.(defineProperty|defineProperties|getOwnPropertyDescriptors?|getOwnPropertyNames|setPrototypeOf)\\b",
|
||||
"boxed primitives": "\\bnew\\s+(String|Number|Boolean)\\s*\\(",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user