From 651529d64ee1bd22aec231e5238aeae039ff867e Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:35:20 -0500 Subject: [PATCH] fix(codemode): coerce any value to a property key (#50479) --- packages/codemode/interpreter-support.md | 6 ++++-- .../codemode/src/interpreter/interpreter.ts | 21 ++++++++----------- packages/codemode/test/parity.test.ts | 14 ++++++++++--- packages/codemode/test/test262/skipped.txt | 7 ------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/codemode/interpreter-support.md b/packages/codemode/interpreter-support.md index d8851ae8c9f..a60f6f05e3c 100644 --- a/packages/codemode/interpreter-support.md +++ b/packages/codemode/interpreter-support.md @@ -243,7 +243,7 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262 accepted, including `.then`/`.catch` handlers and collection callbacks, and vanish at the data boundary like any function. - [x] `Promise.withResolvers()`: the same promise and resolver callables as the constructor, as a `{ promise, resolve, - reject }` object. +reject }` object. - [x] `Promise.try(fn, ...args)`: calls `fn` synchronously; a throw rejects, a return fulfils, and a returned promise or thenable is adopted. - [x] Recursive assimilation of objects with an own callable `then` field across `Promise.resolve`, combinators, @@ -266,7 +266,9 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262 not construct. - [x] `Object()` and `new Object()` return `{}` for nullish arguments and pass objects through unchanged; primitive wrapper objects (`Object(1)`) are rejected explicitly. -- [x] Computed property names and object spread. +- [x] Computed property names and object spread. Any value works as a key (ToPropertyKey): strings, numbers, and the + two confined symbols as themselves, everything else as its string form (`o[null]` is `o["null"]`, `o[{}]` is + `o["[object Object]"]`), in reads, writes, literals, `in`, and destructuring. - [x] `Object.keys`, `Object.values`, `Object.entries`, `Object.hasOwn`, `Object.assign`, and `Object.fromEntries`, with synchronous iterator support for `fromEntries`. Sources follow ToObject: strings enumerate by index, other primitives and wrappers contribute nothing, and `null`/`undefined` throw. `Object.assign` accepts array diff --git a/packages/codemode/src/interpreter/interpreter.ts b/packages/codemode/src/interpreter/interpreter.ts index 9edb71c74a5..11dccc097e4 100644 --- a/packages/codemode/src/interpreter/interpreter.ts +++ b/packages/codemode/src/interpreter/interpreter.ts @@ -1244,7 +1244,7 @@ class Frame { } const keyNode = property.key if (property.computed) { - return Effect.map(this.evaluateExpression(keyNode), (value) => this.toPropertyKey(value, keyNode)) + return Effect.map(this.evaluateExpression(keyNode), (value) => this.toPropertyKey(value)) } if (keyNode.type === "Identifier") return Effect.succeed(keyNode.name) if (keyNode.type === "Literal") return Effect.succeed(String(keyNode.value)) @@ -1943,11 +1943,11 @@ class Frame { let key: PropertyKey if (property.computed) { - key = self.toPropertyKey(yield* self.evaluateExpression(keyNode), keyNode) + key = self.toPropertyKey(yield* self.evaluateExpression(keyNode)) } else if (keyNode.type === "Identifier") { key = keyNode.name } else if (keyNode.type === "Literal") { - key = self.toPropertyKey(literal(keyNode), keyNode) + key = self.toPropertyKey(literal(keyNode)) } else { throw typeError("Unsupported object property key shape.", keyNode) } @@ -2048,10 +2048,10 @@ class Frame { if ((objectValue === null || objectValue === undefined) && node.optional) return OptionalShortCircuit const key = node.computed - ? self.toPropertyKey(yield* self.evaluateExpression(propertyNode), propertyNode) + ? self.toPropertyKey(yield* self.evaluateExpression(propertyNode)) : propertyNode.type === "Identifier" ? propertyNode.name - : self.toPropertyKey(yield* self.evaluateExpression(propertyNode), propertyNode) + : self.toPropertyKey(yield* self.evaluateExpression(propertyNode)) if (objectValue instanceof ToolReference) { if (typeof key !== "string") { @@ -2168,12 +2168,9 @@ class Frame { throw typeError(`Cannot assign to read only property '${String(key)}'.`, node) } - private toPropertyKey(value: Value, node: AstNode): PropertyKey { - if (typeof value === "string" || typeof value === "number") { - return value - } - if (value === AsyncIteratorSymbol || value === IteratorSymbol) return value - - throw typeError("Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.", node) + // ToPropertyKey: anything else becomes its string form, so `counts[row.category]` works when the field is null. + private toPropertyKey(value: Value): PropertyKey { + if (typeof value === "string" || typeof value === "number" || typeof value === "symbol") return value + return coerceToString(value) } } diff --git a/packages/codemode/test/parity.test.ts b/packages/codemode/test/parity.test.ts index 6f8e2fd0659..4b467dee7b0 100644 --- a/packages/codemode/test/parity.test.ts +++ b/packages/codemode/test/parity.test.ts @@ -784,9 +784,17 @@ describe("destructuring assignment", () => { ).toEqual({ declared: "a", declarationRest: { 1: "b" }, assigned: "c", assignmentRest: { 1: "d" } }) }) - test("rejects computed keys that are not confined property keys", async () => { - const err = await error(`const key = {}; const { [key]: value } = {}`) - expect(err.message).toContain("Property key must be a string or number") + test("computed keys of any type become their string form, as in JS", async () => { + expect( + await value(` + const counts = {} + for (const category of ["a", null, undefined, "a", true, 1.5]) counts[category] = (counts[category] ?? 0) + 1 + const key = {} + const { [key]: value } = { "[object Object]": 7 } + const o = { null: 1, "1,2": 2 } + return [counts, value, o[null], o[[1, 2]], undefined in o] + `), + ).toEqual([{ a: 2, null: 1, undefined: 1, true: 1, "1.5": 1 }, 7, 1, 2, false]) }) }) diff --git a/packages/codemode/test/test262/skipped.txt b/packages/codemode/test/test262/skipped.txt index 631becd0645..fd2f9771b53 100644 --- a/packages/codemode/test/test262/skipped.txt +++ b/packages/codemode/test/test262/skipped.txt @@ -16,7 +16,6 @@ built-ins/Array/prototype/copyWithin/return-abrupt-from-target.js # Expected a built-ins/Array/prototype/every/15.4.4.16-7-6.js # res Expected SameValue(«true», «false») to be true built-ins/Array/prototype/every/15.4.4.16-7-c-i-8.js # [, , , ].every(callbackfn) Expected SameValue(«true», «false») to be true built-ins/Array/prototype/every/15.4.4.16-8-10.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/every/15.4.4.16-8-13.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/every/15.4.4.16-8-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/every/15.4.4.16-8-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/every/15.4.4.16-8-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct @@ -28,7 +27,6 @@ built-ins/Array/prototype/fill/coerced-indexes.js # Array.fill expects start to built-ins/Array/prototype/fill/return-abrupt-from-end.js # Expected a Test262Error but got a TypeError built-ins/Array/prototype/fill/return-abrupt-from-start.js # Expected a Test262Error but got a TypeError built-ins/Array/prototype/filter/15.4.4.20-10-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/filter/15.4.4.20-10-4.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/filter/15.4.4.20-6-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/filter/15.4.4.20-6-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/filter/15.4.4.20-6-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct @@ -44,7 +42,6 @@ built-ins/Array/prototype/flat/non-object-ctor-throws.js # null value Expected built-ins/Array/prototype/forEach/15.4.4.18-7-5.js # callCnt Expected SameValue(«4», «5») to be true built-ins/Array/prototype/forEach/15.4.4.18-7-c-i-8.js # testResult !== true built-ins/Array/prototype/forEach/15.4.4.18-8-10.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/forEach/15.4.4.18-8-12.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/forEach/15.4.4.18-8-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/forEach/15.4.4.18-8-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/forEach/15.4.4.18-8-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct @@ -107,7 +104,6 @@ built-ins/Array/prototype/lastIndexOf/length-zero-returns-minus-one.js # Array. built-ins/Array/prototype/map/15.4.4.19-8-6.js # resArr[4] Expected SameValue(«undefined», «1») to be true built-ins/Array/prototype/map/15.4.4.19-8-c-i-8.js # newArr[1] Expected SameValue(«13», «true») to be true built-ins/Array/prototype/map/15.4.4.19-9-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/map/15.4.4.19-9-4.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/map/create-ctor-non-object.js # null value Expected a TypeError to be thrown but no exception was thrown at all built-ins/Array/prototype/pop/S15.4.4.6_A2_T1.js # Array.prototype.pop called on incompatible receiver a data object. built-ins/Array/prototype/pop/S15.4.4.6_A2_T4.js # Array.prototype.pop called on incompatible receiver a data object. @@ -127,7 +123,6 @@ built-ins/Array/prototype/reduce/15.4.4.21-10-3.js # … cannot be constructed: built-ins/Array/prototype/reduce/15.4.4.21-10-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduce/15.4.4.21-10-6.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduce/15.4.4.21-10-7.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/reduce/15.4.4.21-10-8.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/reduce/15.4.4.21-5-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduce/15.4.4.21-5-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduce/15.4.4.21-5-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct @@ -151,7 +146,6 @@ built-ins/Array/prototype/reduceRight/15.4.4.22-10-3.js # … cannot be constru built-ins/Array/prototype/reduceRight/15.4.4.22-10-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduceRight/15.4.4.22-10-6.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduceRight/15.4.4.22-10-7.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/reduceRight/15.4.4.22-10-8.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/reduceRight/15.4.4.22-5-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduceRight/15.4.4.22-5-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/reduceRight/15.4.4.22-5-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct @@ -197,7 +191,6 @@ built-ins/Array/prototype/slice/create-ctor-non-object.js # null value Expected built-ins/Array/prototype/some/15.4.4.17-7-6.js # res Expected SameValue(«false», «true») to be true built-ins/Array/prototype/some/15.4.4.17-7-c-i-8.js # [, ].some(callbackfn) !== true built-ins/Array/prototype/some/15.4.4.17-8-10.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct -built-ins/Array/prototype/some/15.4.4.17-8-13.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator. built-ins/Array/prototype/some/15.4.4.17-8-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/some/15.4.4.17-8-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct built-ins/Array/prototype/some/15.4.4.17-8-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct