diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index b84c0594e..9a8f1121c 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -3891,6 +3891,9 @@ function isJsonSafeAt( const shape = getRecord(t.shapeId); if (!shape) return false; if (recordTextCodecClass(shape) !== null) return false; + // Accessor slots are not function-valued properties: Node's + // JSON.stringify invokes the getter (see accessorSlotProp). + if (shapeHasAccessorSlots(shape)) return false; // The recursive knot: answer true and let the rest of the graph // decide (any unsafe constituent ends the traversal immediately). if (visiting.has(t.shapeId)) return true; @@ -4228,6 +4231,9 @@ function canBoxDynCompositeAt( const shape = getRecord(t.shapeId); if (!shape) return false; if (recordTextCodecClass(shape) !== null) return false; + // Accessor slots hold the getter and setter closures, not property + // values (see accessorSlotProp). + if (shapeHasAccessorSlots(shape)) return false; // Recursive shapes answer coinductively, like isJsonSafeType. if (visiting.has(t.shapeId)) return true; visiting.add(t.shapeId); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json new file mode 100644 index 000000000..08ae4c161 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/object-literal-accessor-values.ts" + ], + "diags": [] +} diff --git a/tests/diagnostics/object-literal-accessor-values.ts b/tests/diagnostics/object-literal-accessor-values.ts new file mode 100644 index 000000000..107ab1f69 --- /dev/null +++ b/tests/diagnostics/object-literal-accessor-values.ts @@ -0,0 +1,31 @@ +// Object literals with get/set accessors keep their closures in reserved +// slots, so their typed values have no JSON or checked-dynamic form: Node's +// JSON.stringify and dynamic reads invoke the getter, which neither a typed +// serializer nor a converted copy could do. + +function hold(value: unknown): unknown { + return value; +} + +const counter = { + count: 1, + get doubled(): number { + return 2; + }, +}; + +// JSON.stringify of the typed value would omit the getter's property. +console.log(JSON.stringify(counter)); + +// Converting the typed value to `unknown` or `any` would lose the getter. +hold(counter); +const viaAny: any = counter; +console.log(viaAny); + +// A literal written into an `any` slot directly keeps its accessor. +const direct: any = { + get value(): number { + return 3; + }, +}; +console.log(direct.value); diff --git a/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt b/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt new file mode 100644 index 000000000..89bc092aa --- /dev/null +++ b/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt @@ -0,0 +1,24 @@ +object-literal-accessor-values.ts:18:28 - error SC1090: JSON.stringify of '{ get doubled(): number; count: number }' values (only number, string, boolean, records, arrays, unions of those, and 'unknown' stringify) are not supported yet + + 17 | // JSON.stringify of the typed value would omit the getter's property. + 18 | console.log(JSON.stringify(counter)); + | ^~~~~~~ + 19 | + +object-literal-accessor-values.ts:21:6 - error SC1101: converting typed values to 'unknown' is not supported yet + + 20 | // Converting the typed value to `unknown` or `any` would lose the getter. + 21 | hold(counter); + | ^~~~~~~ + 22 | const viaAny: any = counter; + + hint: numbers, strings, booleans, JSON-safe records/arrays/unions (a deep copy), functions, program class instances, and supported native handles convert into 'unknown' slots; functions, exact-class round trips, and handles preserve identity, while this value's type has no dynamic representation yet + +object-literal-accessor-values.ts:22:21 - error SC1101: converting typed values to 'unknown' is not supported yet + + 21 | hold(counter); + 22 | const viaAny: any = counter; + | ^~~~~~~ + 23 | console.log(viaAny); + + hint: numbers, strings, booleans, JSON-safe records/arrays/unions (a deep copy), functions, program class instances, and supported native handles convert into 'unknown' slots; functions, exact-class round trips, and handles preserve identity, while this value's type has no dynamic representation yet \ No newline at end of file