Compare commits

...
1 Commits
12 changed files with 218 additions and 52 deletions
+21 -10
View File
@@ -55,7 +55,7 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
- [x] Tagged templates: a tag applied to a template literal is called as `tag(strings, ...values)`, with the tag read
like a callee so a member tag keeps its receiver. `strings` is an array of the cooked text with a read-only `raw`
array of the source text; an invalid escape such as `\unicode` cooks to `undefined`. One template object per
site, as in JS, but it is not frozen: `strings[0] = "x"` succeeds here where JS throws.
site and both arrays are frozen, as in JS: `strings[0] = "x"` throws a `TypeError`.
- [x] Regular-expression literals.
- [x] `NaN` and `Infinity` globals.
- [ ] BigInt literals and in-interpreter BigInt arithmetic; BigInt remains invalid at JSON-like host boundaries.
@@ -101,9 +101,11 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
- [x] `for`, `while`, and `do...while`.
- [x] `for...of` over arrays, strings, Maps, Sets, URLSearchParams, Headers, Uint8Arrays, built-in iterators, custom
synchronous iterators, and confined synchronous generators. Abrupt completion invokes the iterator's optional `return()`.
- [x] `for...in` over own keys of plain objects, arrays, strings, and tool references. `null`, `undefined`, and other
non-objects iterate nothing. An un-awaited promise throws rather than iterating.
- [ ] `for...in` over inherited enumerable keys (`Object.create(proto)`), and skipping keys deleted during the loop.
- [x] `for...in` over the enumerable keys of plain objects, arrays, strings, and tool references, following the
prototype chain like JS (`for (k in Object.create({ a: 1 }))` visits `a`; built-in prototype methods are
non-enumerable so `for (k in [])` visits nothing). A key deleted before its turn is skipped and keys added during
the loop are not visited. `null`, `undefined`, and other non-objects iterate nothing. An un-awaited promise
throws rather than iterating.
- [x] Unlabeled `break` and `continue`.
- [x] `try`, `catch`, optional catch bindings, and `finally`.
- [x] `throw` with arbitrary values.
@@ -170,8 +172,11 @@ Math.floor)` is `"3"`). A detached method loses its receiver, as in JS: `values.
- [x] Redeclaring a function in the same scope, or alongside a `var`, is allowed: the last declaration wins.
- [x] Generator functions have their own `prototype` (inheriting the shared generator prototype), so
`g() instanceof g` holds. Plain functions have none, since they cannot construct.
- [ ] `GeneratorFunction.prototype`: every function, generator or not, inherits directly from `Function.prototype`,
and a generator whose `prototype` was replaced by a non-object still creates from the shared generator prototype.
- [x] Generator functions inherit from `GeneratorFunction.prototype` (async ones from
`AsyncGeneratorFunction.prototype`), an ordinary non-callable object under `Function.prototype` whose `prototype`
is the shared generator prototype and vice versa (`Object.getPrototypeOf(g).prototype.constructor`). Neither is
a global; async non-generator functions still inherit from `Function.prototype` directly. A generator whose
`prototype` was replaced by a non-object creates from the shared generator prototype.
- [x] Generator and async generator functions bind parameters (defaults, destructuring) at the call and defer only the
body to the first `next()`, so a bad argument throws synchronously from the call site, as in JS.
- [x] Synchronous and async generator declarations/expressions, `yield`, and `yield*`, including lazy bodies,
@@ -247,9 +252,13 @@ Math.floor)` is `"3"`). A detached method loses its receiver, as in JS: `values.
hint (`"abc".indexOf({ toString() { return "b" } })` is `1`, `(255).toString({ valueOf() { return 16 } })` is
`"ff"`, `String.prototype.trim.call({ toString() { return " a " } })` is `"a"`). Only consumed positions
convert; a RegExp pattern is used as is, and `includes`/`startsWith`/`endsWith` reject one before converting.
- [ ] ToPrimitive elsewhere: `Error.prototype.toString` on an object `message` and numeric arguments of the Array and
Uint8Array methods (`at`, `indexOf` start, `slice`) still use the built-in form (`NaN`, `"[object Object]"`) and
ignore own methods.
- [x] `Error.prototype.toString` converts an object `name` or `message` through its own `toString` (`String(e)` with
`e.message = { toString() { return "m" } }` is `"Error: m"`); an uncaught error's report at the result boundary
still uses the built-in form.
- [x] `Array.prototype.toString` calls `this.join`, so `arr.join = () => "j"` makes `arr + ""`, `String(arr)`, and
`${arr}` all `"j"`; a non-callable `join` gives `"[object Array]"`.
- [ ] ToPrimitive elsewhere: numeric arguments of the Array and Uint8Array methods (`at`, `indexOf` start, `slice`)
still use the built-in form (`NaN`) and ignore own methods.
- [x] Property keys follow ToPropertyKey: `x[null]` and `x[true]` become string keys, and a data object key
converts through its own `toString`/`valueOf` (string hint) exactly once per access, in reads, writes,
compound assignment, `++`, `delete`, `in`, object literals, and destructuring:
@@ -259,6 +268,8 @@ Math.floor)` is `"3"`). A detached method loses its receiver, as in JS: `values.
## Promises and tools
- [x] Tool calls start eagerly and return supervised, run-once CodeMode promises.
- [x] Tool references have identity: `tools.x === tools.x` and `tools.ns === tools.ns`, so they work as `Map`/`Set`
members and `switch` cases like any other object.
- [x] Direct `await`, repeated awaits, and recursive thenable assimilation when a promise or thenable is returned from
a function/program.
- [x] `Promise.resolve` and `Promise.reject`.
@@ -335,7 +346,7 @@ reject }` object.
`TypeError`.
- [x] `Object.create(proto)` with an object or `null` prototype; any other prototype is a `TypeError`
(`Object prototype may only be an Object or null`). Inherited reads, `in`, `hasOwnProperty`, and own-only
`Object.keys` follow the chain as in JS, but `for...in` still enumerates own keys only. A second `properties`
`Object.keys` follow the chain as in JS, and `for...in` enumerates inherited keys too. A second `properties`
argument other than `undefined` throws a `TypeError`: property descriptors are not supported (there is no
`Object.defineProperty` either).
- [x] `Object.freeze`, `Object.seal`, and `Object.preventExtensions`, with `isFrozen`, `isSealed`, and `isExtensible`.
+15 -6
View File
@@ -20,7 +20,7 @@ import {
type Value,
} from "./objects.js"
import type { Interpreter } from "./interpreter.js"
import { toPrimitiveString } from "./callback.js"
import { toPrimitiveString, withPrimitives } from "./callback.js"
import { formatValue } from "../stdlib/console.js"
export const normalizeError = (error: unknown): Diagnostic => {
@@ -48,7 +48,9 @@ export const normalizeError = (error: unknown): Diagnostic => {
if (error instanceof Throw) {
const value = error.value
if (value instanceof ErrorObj) {
return value.host ? normalizeError(value.host) : { kind: "ExecutionFailure", message: errorToString(value) }
return value.host
? normalizeError(value.host)
: { kind: "ExecutionFailure", message: errorToString(get(value, "name"), get(value, "message")) }
}
let message: string
if (containsRuntimeReference(value)) {
@@ -113,9 +115,7 @@ export const materialize = <R>(ctx: Interpreter<R>, thrown: unknown): Value => {
}
/** Error.prototype.toString: `name: message`, omitting whichever side is empty. */
const errorToString = (self: Obj): string => {
const name = get(self, "name")
const message = get(self, "message")
const errorToString = (name: Value, message: Value): string => {
const shownName = name === undefined ? "Error" : coerceToString(name)
const shownMessage = message === undefined ? "" : coerceToString(message)
if (shownMessage === "") return shownName
@@ -179,7 +179,16 @@ export const errorGlobal = <R>(type: ErrorType, ctx: Interpreter<R>) => {
})
if (type === "Error") {
methods(builtins, prototype, [
["toString", 0, (thisValue) => errorToString(receiver(Obj, thisValue, "Error.prototype.toString"))],
[
"toString",
0,
(thisValue) => {
const self = receiver(Obj, thisValue, "Error.prototype.toString")
return withPrimitives(ctx, "string", [get(self, "name"), get(self, "message")], ([name, message]) =>
errorToString(name, message),
)
},
],
])
methods(builtins, ctor, [["isError", 1, (_, args) => args[0] instanceof ErrorObj]])
return ctor
@@ -79,7 +79,8 @@ import {
has,
hidden,
hasPrototype,
keys,
ownKeys,
enumerable,
Native,
parseArrayIndex,
Arguments,
@@ -100,7 +101,7 @@ import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js"
import { describeValue, isOpaque, rejectCircularInsertion, typeofValue } from "./references.js"
import { ScopeStack } from "./scope.js"
import { constructRegExp } from "../stdlib/regexp.js"
import { enumerableSource } from "../stdlib/object.js"
import { enumerableSource, restrict } from "../stdlib/object.js"
import { compoundOperators } from "../stdlib/value.js"
/** The binary operators that convert object operands through ToPrimitive before acting on primitives. */
@@ -506,7 +507,7 @@ class Frame<R> {
): Fn {
const builtins = this.ctx.builtins
const fn = new Fn(
builtins.Function,
node.generator ? (node.async ? builtins.AsyncGeneratorFunction : builtins.GeneratorFunction) : builtins.Function,
name,
node.params,
node.body,
@@ -955,10 +956,24 @@ class Frame<R> {
}
// for...in over null/undefined iterates nothing, like JS.
// EnumerateObjectProperties: own keys, then each prototype's, visiting a shadowed key once.
private enumerableKeys(value: Value, node: AstNode): Array<string> {
if (value instanceof ToolReference) return [...this.ctx.tools.keys(value.path)]
if (value === null || value === undefined) return []
return keys(enumerableSource(this.ctx, "for...in", value, node))
const seen = new Set<string>()
const result: Array<string> = []
for (
let current: Obj | null = enumerableSource(this.ctx, "for...in", value, node);
current !== null;
current = current.proto
) {
for (const key of ownKeys(current)) {
if (typeof key !== "string" || seen.has(key)) continue
seen.add(key)
if (enumerable(current, key)) result.push(key)
}
}
return result
}
private evaluateForInStatement(
@@ -982,6 +997,8 @@ class Frame<R> {
const assignment = left.type === "VariableDeclaration" ? undefined : left
for (const key of keys) {
// A key deleted before its turn is skipped, as in JS.
if (right instanceof Obj && !has(right, key)) continue
const result = yield* Effect.gen(function* () {
if (declared?.lexical) {
self.scopes.push()
@@ -1710,12 +1727,15 @@ class Frame<R> {
})
}
// Built-ins throw without a location, synchronously or inside their Effect; the call site supplies it.
// Built-ins throw without a location, synchronously or inside their Effect; the call site supplies it. A built-in
// reached through `ctx.call` runs on the root frame, so the deeper of the frame and the enclosing site counts.
private native(body: () => Effect.Effect<Value, unknown, R>, node?: AstNode): Effect.Effect<Value, unknown, R> {
return Effect.provideService(
Effect.catchDefect(Effect.suspend(body), (defect) => Effect.die(locate(defect, node))),
CallSite,
{ node, depth: this.depth },
return Effect.flatMap(CallSite, (site) =>
Effect.provideService(
Effect.catchDefect(Effect.suspend(body), (defect) => Effect.die(locate(defect, node))),
CallSite,
{ node, depth: Math.max(this.depth, site.depth) },
),
)
}
@@ -2155,12 +2175,16 @@ class Frame<R> {
define(
strings,
"raw",
new Arr(
array,
node.quasi.quasis.map((quasi) => quasi.value.raw),
restrict(
"freeze",
new Arr(
array,
node.quasi.quasis.map((quasi) => quasi.value.raw),
),
),
frozen,
)
restrict("freeze", strings)
this.ctx.templates.set(node, strings)
return strings
}
@@ -2208,7 +2232,7 @@ class Frame<R> {
if (typeof key !== "string") {
throw typeError("Tool paths must use string property names.", propertyNode)
}
return new ToolReference([...objectValue.path, key])
return objectValue.child(key)
}
if (objectValue instanceof Obj) return { target: objectValue, key, receiver: objectValue }
@@ -1,5 +1,5 @@
import { Effect } from "effect"
import { define, hidden, Native, Arr, ErrorObj, Obj, type Value } from "./objects.js"
import { Arr, define, ErrorObj, hidden, Native, Obj, readOnly, type Value } from "./objects.js"
export const errorTypes = [
"Error",
@@ -41,6 +41,8 @@ const builtins = [
"AsyncIterator",
"Generator",
"AsyncGenerator",
"GeneratorFunction",
"AsyncGeneratorFunction",
] as const
/**
@@ -79,6 +81,15 @@ export const createBuiltins = (): Builtins => {
}
const iterator = plain()
const asyncIterator = plain()
// %GeneratorFunction.prototype% is an ordinary object linked both ways with %GeneratorPrototype%.
const generatorFunction = (generator: Obj) => {
const proto = new Obj(fn)
define(proto, "prototype", generator, readOnly)
define(generator, "constructor", proto, readOnly)
return proto
}
const generator = new Obj(iterator)
const asyncGenerator = new Obj(asyncIterator)
return {
Object: object,
Function: fn,
@@ -102,8 +113,10 @@ export const createBuiltins = (): Builtins => {
Iterator: iterator,
IteratorHelper: new Obj(iterator),
AsyncIterator: asyncIterator,
Generator: new Obj(iterator),
AsyncGenerator: new Obj(asyncIterator),
Generator: generator,
AsyncGenerator: asyncGenerator,
GeneratorFunction: generatorFunction(generator),
AsyncGeneratorFunction: generatorFunction(asyncGenerator),
Error: error,
TypeError: derived("TypeError"),
RangeError: derived("RangeError"),
+2 -1
View File
@@ -36,6 +36,7 @@ export const hidden: Attributes = { writable: true, enumerable: false, configura
export const readonly: Attributes = { writable: false, enumerable: false, configurable: true }
/** Constants such as `Math.PI` and a constructor's `prototype`. */
export const frozen: Attributes = { writable: false, enumerable: false, configurable: false }
export const readOnly: Attributes = { writable: false, enumerable: false, configurable: true }
/**
* An object owned by the program: own properties plus a prototype link. Subclasses answer, in one place, how a
@@ -645,7 +646,7 @@ export const ownKeys = (target: Obj): Array<string | symbol> => {
]
}
const enumerable = (target: Obj, key: string | symbol): boolean => own(target, key)?.enumerable === true
export const enumerable = (target: Obj, key: string | symbol): boolean => own(target, key)?.enumerable === true
/** Own enumerable keys, including the iterator symbols; what spread and `Object.assign` copy. */
export const enumerableKeys = (target: Obj): Array<string | symbol> =>
+7 -7
View File
@@ -5,6 +5,7 @@ import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpret
import {
define,
get,
Callable,
hidden,
Arr,
GeneratorObj,
@@ -165,13 +166,12 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
[
"toString",
0,
(thisValue) =>
withPrimitives(
ctx,
"string",
Array.from(self(thisValue, "toString").items, (item) => item ?? ""),
(items) => items.map(coerceToString).join(","),
),
(thisValue) => {
// Spec: delegate to this.join, so an overridden join shows up in `arr + ""` and String(arr).
const target = self(thisValue, "toString")
const join = get(target, "join")
return join instanceof Callable ? ctx.call(join, target, []) : `[object ${target.tag}]`
},
],
[
"includes",
+1 -1
View File
@@ -104,7 +104,7 @@ const propertyKey = (value: Value): PropertyKey =>
// SetIntegrityLevel: primitives pass through. A typed array's bytes cannot carry attributes, so JS throws after
// already making it non-extensible.
const restrict = (level: "freeze" | "seal" | "preventExtensions", value: Value): Value => {
export const restrict = (level: "freeze" | "seal" | "preventExtensions", value: Value): Value => {
if (!(value instanceof Obj)) return value
value.extensible = false
if (level === "preventExtensions") return value
+9
View File
@@ -113,7 +113,16 @@ export const toolExpression = (path: string) =>
.join("")
export class ToolReference {
private readonly children = new Map<string, ToolReference>()
constructor(readonly path: ReadonlyArray<string>) {}
/** One reference per path, so `tools.a === tools.a` holds like any other member read. */
child(key: string): ToolReference {
const existing = this.children.get(key)
if (existing !== undefined) return existing
const created = new ToolReference([...this.path, key])
this.children.set(key, created)
return created
}
}
// Dots in tool names are namespace separators; the last tool for a canonical path wins.
+19
View File
@@ -178,6 +178,25 @@ describe("call depth", () => {
expect(Date.now() - started).toBeLessThan(2000)
})
test("recursion routed through nested built-ins keeps counting depth", async () => {
const started = Date.now()
expect(
await value(`
const a = [1]
a.join = () => a + ""
const o = { toString() { return [o].map(String)[0] } }
const e = new Error()
e.message = { toString() { return String(e) } }
const names = []
for (const run of [() => String(a), () => String(o), () => String(e)]) {
try { run() } catch (error) { names.push(error.name) }
}
return names
`),
).toEqual(["RangeError", "RangeError", "RangeError"])
expect(Date.now() - started).toBeLessThan(2000)
})
test("uncaught overflow reports the call that overflowed", async () => {
const failure = await error(`const f = (n) => f(n + 1); return f(0)`)
expect(failure.kind).toBe("ExecutionFailure")
+78 -2
View File
@@ -1382,7 +1382,7 @@ describe("Object.getPrototypeOf and Object.create", () => {
expect((await error(`Object.getPrototypeOf(Symbol.iterator)`)).message).toContain("cannot convert a symbol")
})
test("Object.create links the prototype: inherited reads, in, and own-only keys", async () => {
test("Object.create links the prototype: inherited reads, in, own-only keys, and for...in", async () => {
expect(
await value(`
const p = { greet(name) { return "hi " + name }, a: 1 }
@@ -1392,7 +1392,7 @@ describe("Object.getPrototypeOf and Object.create", () => {
for (const key in c) seen.push(key)
return ["greet" in c, Object.keys(c), c.hasOwnProperty("a"), c.a, c.greet(c.name), Object.getPrototypeOf(c) === p, Object.getPrototypeOf(Object.create(null)), seen]
`),
).toEqual([true, ["name"], false, 1, "hi x", true, null, ["name"]])
).toEqual([true, ["name"], false, 1, "hi x", true, null, ["name", "greet", "a"]])
})
test("Object.create rejects non-object prototypes and property descriptors", async () => {
@@ -1984,3 +1984,79 @@ describe("WeakMap and WeakSet", () => {
expect((await error(`structuredClone(new WeakSet())`)).message).toContain("DataCloneError")
})
})
describe("small language leftovers", () => {
test("for...in walks the prototype chain and skips keys deleted before their turn", async () => {
expect(
await value(`
const o = Object.create({ a: 1, shadowed: 1 })
o.b = 2
o.shadowed = 3
const keys = []
for (const k in o) keys.push(k)
const live = { a: 1, b: 2, c: 3 }
const seen = []
for (const k in live) { seen.push(k); delete live.b; live.z = 1 }
const none = []
for (const k in []) none.push(k)
for (const k in new TypeError("x")) none.push(k)
return [keys, seen, none]
`),
).toEqual([["b", "shadowed", "a"], ["a", "c"], []])
})
test("tagged template objects are frozen", async () => {
expect(
await value(`
const tag = (s) => s
const f = () => tag\`a\${1}b\`
return [f() === f(), Object.isFrozen(f()), Object.isFrozen(f().raw)]
`),
).toEqual([true, true, true])
expect((await error("const tag = (s) => s; tag`a`[0] = 'x'")).message).toContain("read only")
})
test("Array.prototype.toString delegates to join", async () => {
expect(
await value(`
const a = [1, 2]
a.join = () => "j"
const b = [1]
b.join = 5
return [a + "", String(a), \`\${a}\`, b.toString(), Array.prototype.toString.call([3, [4]])]
`),
).toEqual(["j", "j", "j", "[object Array]", "3,4"])
})
test("Error.prototype.toString converts object name and message", async () => {
expect(
await value(`
const e = new Error("m")
e.message = { toString() { return "obj" } }
e.name = { valueOf() { return "N" }, toString() { return "T" } }
return [String(e), Error.prototype.toString.call({ name: "", message: "m" }), Error.prototype.toString.call({})]
`),
).toEqual(["T: obj", "m", "Error"])
expect(
(await error(`const e = new Error(); e.message = { toString() { throw new RangeError("r") } }; String(e)`))
.message,
).toContain("r")
})
test("generator functions inherit from GeneratorFunction.prototype", async () => {
expect(
await value(`
function* g() {}
async function* ag() {}
const GFP = Object.getPrototypeOf(g)
g.prototype = null
return [
typeof GFP, GFP === Function.prototype, Object.getPrototypeOf(GFP) === Function.prototype,
GFP.prototype.constructor === GFP, Object.getPrototypeOf(ag) === GFP, typeof g.bind,
Object.getPrototypeOf(g()) === GFP.prototype,
]
`),
).toEqual(["object", false, true, true, false, "function", true])
expect((await error(`function* g() {} Object.getPrototypeOf(g)()`)).message).toContain("not a function")
})
})
@@ -884,7 +884,6 @@ built-ins/AsyncGeneratorFunction/invoked-as-constructor-no-arguments.js # TypeE
built-ins/AsyncGeneratorFunction/invoked-as-function-multiple-arguments.js # TypeError: The Function constructor is not supported; write the function inline.
built-ins/AsyncGeneratorFunction/invoked-as-function-no-arguments.js # TypeError: The Function constructor is not supported; write the function inline.
built-ins/AsyncGeneratorFunction/invoked-as-function-single-argument.js # TypeError: The Function constructor is not supported; write the function inline.
built-ins/AsyncGeneratorFunction/prototype/not-callable.js # Expected SameValue(«"function"», «"object"») to be true
built-ins/AsyncGeneratorPrototype/next/this-val-not-async-generator.js # TypeError: Cannot read properties of undefined (reading '…').
built-ins/AsyncGeneratorPrototype/return/this-val-not-async-generator.js # TypeError: Cannot read properties of undefined (reading '…').
built-ins/AsyncGeneratorPrototype/throw/this-val-not-async-generator.js # TypeError: Cannot read properties of undefined (reading '…').
@@ -900,7 +899,6 @@ built-ins/Date/prototype/toJSON/invoke-result.js # TypeError: Date.prototype.to
built-ins/Date/prototype/toJSON/to-primitive-value-of.js # TypeError: Date.prototype.toJSON called on incompatible receiver a data object.
built-ins/Date/prototype/toString/format.js # Expected SameValue(«null», «null») to be false
built-ins/Date/prototype/toString/negative-year.js # Date.prototype.toString serializes year -1 to "-0001" Expected SameValue(«undefined», «"-0001"») to
built-ins/Error/prototype/toString/tostring-message-throws-toprimitive.js # ToPrimitive(msg) called by ToString(msg) throws TypeError Expected a TypeError to be thrown but no e
built-ins/Function/15.3.5-1gs.js # Expected a TypeError to be thrown but no exception was thrown at all
built-ins/Function/15.3.5-2gs.js # Expected a TypeError to be thrown but no exception was thrown at all
built-ins/Function/15.3.5.4_2-1gs.js # Expected a TypeError to be thrown but no exception was thrown at all
@@ -931,7 +929,6 @@ built-ins/GeneratorFunction/invoked-as-constructor-no-arguments.js # TypeError:
built-ins/GeneratorFunction/invoked-as-function-multiple-arguments.js # TypeError: The Function constructor is not supported; write the function inline.
built-ins/GeneratorFunction/invoked-as-function-no-arguments.js # TypeError: The Function constructor is not supported; write the function inline.
built-ins/GeneratorFunction/invoked-as-function-single-argument.js # TypeError: The Function constructor is not supported; write the function inline.
built-ins/GeneratorFunction/prototype/not-callable.js # Expected SameValue(«"function"», «"object"») to be true
built-ins/GeneratorPrototype/throw/from-state-completed.js # Expected a E but got a TypeError
built-ins/GeneratorPrototype/throw/from-state-suspended-start.js # Expected a E but got a TypeError
built-ins/Iterator/from/return-method-returns-iterator-result.js # Iterator next must be a function.
@@ -1468,12 +1465,10 @@ language/expressions/function/dstr/ary-init-iter-get-err-array-prototype.js # E
language/expressions/function/dstr/ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/expressions/function/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/expressions/function/dstr/dflt-ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/expressions/generators/default-proto.js # Expected SameValue(«object», «undefined») to be true
language/expressions/generators/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/expressions/generators/dstr/ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/expressions/generators/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/expressions/generators/dstr/dflt-ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/expressions/generators/prototype-relation-to-function.js # Expected SameValue(«object», «») to be true
language/expressions/greater-than-or-equal/S11.8.4_A3.2_T1.2.js # TypeError: Binary operators require data values.
language/expressions/greater-than/S11.8.2_A3.2_T1.2.js # TypeError: Binary operators require data values.
language/expressions/in/S8.12.6_A2_T2.js # TypeError: Robin cannot be constructed: user-defined constructors and classes are not supported. Cal
@@ -1559,7 +1554,6 @@ language/expressions/super/prop-expr-obj-key-err.js # unsupported syntax Super
language/expressions/super/prop-expr-obj-ref-strict.js # unsupported syntax Super
language/expressions/super/prop-expr-obj-unresolvable.js # Expected SameValue(«SyntaxError», «ReferenceError») to be true
language/expressions/tagged-template/constructor-invocation.js # The called value cannot be constructed: user-defined constructors and classes are not supported.
language/expressions/tagged-template/template-object-frozen-strict.js # Expected a TypeError to be thrown but no exception was thrown at all
language/expressions/this/11.1.1-1.js # Expected SameValue(«undefined», «undefined») to be false
language/expressions/unary-minus/S11.4.7_A3_T5.js # TypeError: Unary operators require data values.
language/expressions/unary-plus/S11.4.6_A3_T5.js # TypeError: Unary operators require data values.
@@ -1588,7 +1582,6 @@ language/statements/async-generator/dstr/dflt-ary-ptrn-elem-id-iter-val-array-pr
language/statements/async-generator/return-undefined-implicit-and-explicit.js # Actual ["tick 1", "tick 2", "g1 ret", "g2 ret", "g3 ret", "g4 ret"] and expected ["tick 1", "g1 ret"
language/statements/const/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/statements/const/dstr/ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/statements/for-in/S12.6.4_A7_T2.js # for...in visits keys deleted during the loop
language/statements/for-in/order-enumerable-shadowed.js # Object.create property descriptors are not supported; assign the fields after creating the object.
language/statements/for-in/S12.6.4_A6.1.js # TypeError: FACTORY cannot be constructed: user-defined constructors and classes are not supported. C
language/statements/for-in/S12.6.4_A6.js # TypeError: FACTORY cannot be constructed: user-defined constructors and classes are not supported. C
@@ -1660,12 +1653,10 @@ language/statements/function/dstr/ary-init-iter-get-err-array-prototype.js # Ex
language/statements/function/dstr/ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/statements/function/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/statements/function/dstr/dflt-ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/statements/generators/default-proto.js # generator functions have no GeneratorFunction.prototype
language/statements/generators/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/statements/generators/dstr/ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/statements/generators/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
language/statements/generators/dstr/dflt-ary-ptrn-elem-id-iter-val-array-prototype.js # Expected SameValue(«3», «42») to be true
language/statements/generators/prototype-relation-to-function.js # generator functions have no GeneratorFunction.prototype
language/statements/generators/restricted-properties.js # Expected a TypeError to be thrown but no exception was thrown at all
language/statements/labeled/value-await-non-module.js # Failed to parse TypeScript: Expression expected.
language/statements/let/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
+13
View File
@@ -368,3 +368,16 @@ describe("tool references under ==", () => {
).toEqual([false, false, false, 0])
})
})
describe("tool reference identity", () => {
test("repeated member reads yield the same reference", async () => {
const runtime = CodeMode.make({ tools: { probe: echo("Probe", "ok"), "ns.inner": echo("Inner", "in") } })
expect(
await value(
runtime,
`return [tools.probe === tools.probe, tools.ns.inner === tools.ns.inner, tools.ns === tools.ns, tools["probe"] === tools.probe,
tools.probe === tools.ns.inner, new Set([tools.probe, tools.probe]).size, await tools.ns.inner({})]`,
),
).toEqual([true, true, true, true, false, 1, "in"])
})
})