From 0e1decdd1e868dc50db8e0d317030447ee47acfa Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Fri, 22 Mar 2024 08:12:06 +0100 Subject: [PATCH] Schema: align runtime tuple behaviour to ts 5.4 (#2389) --- .changeset/proud-pandas-clap.md | 21 +++ packages/schema/src/AST.ts | 8 +- packages/schema/test/Schema/required.test.ts | 152 ++++++++++--------- 3 files changed, 99 insertions(+), 82 deletions(-) create mode 100644 .changeset/proud-pandas-clap.md diff --git a/.changeset/proud-pandas-clap.md b/.changeset/proud-pandas-clap.md new file mode 100644 index 0000000000..dc414ad0fa --- /dev/null +++ b/.changeset/proud-pandas-clap.md @@ -0,0 +1,21 @@ +--- +"@effect/schema": patch +--- + +align runtime tuple behaviour to ts 5.4: + +from + +```ts +// ts 5.3 +type A = readonly [string, ...number[], boolean]; +type B = Required; // readonly [string, ...(number | boolean)[], number | boolean] +``` + +to + +```ts +// ts 5.4 +type A = readonly [string, ...number[], boolean]; +type B = Required; // readonly [string, ...number[], boolean] +``` diff --git a/packages/schema/src/AST.ts b/packages/schema/src/AST.ts index 50bb6edf8f..b59cdaedfa 100644 --- a/packages/schema/src/AST.ts +++ b/packages/schema/src/AST.ts @@ -2104,13 +2104,7 @@ export const required = (ast: AST): AST => { case "TupleType": return new TupleType( ast.elements.map((e) => new Element(e.type, false)), - ReadonlyArray.match(ast.rest, { - onEmpty: () => ast.rest, - onNonEmpty: (rest) => { - const union = Union.make(rest) - return rest.map(() => union) - } - }), + ast.rest, ast.isReadonly ) case "TypeLiteral": diff --git a/packages/schema/test/Schema/required.test.ts b/packages/schema/test/Schema/required.test.ts index 89c7a8ab2c..d15d33fede 100644 --- a/packages/schema/test/Schema/required.test.ts +++ b/packages/schema/test/Schema/required.test.ts @@ -32,105 +32,107 @@ describe("Schema > required", () => { ) }) - it("tuple/ e?", async () => { - // type A = [string?] - // type B = Required - const schema = S.required(S.tuple(S.optionalElement(S.NumberFromString))) - - await Util.expectDecodeUnknownSuccess(schema, ["1"], [1]) - await Util.expectDecodeUnknownFailure( - schema, - [], - `readonly [NumberFromString] + describe("tuple", () => { + it("e?", async () => { + // type A = readonly [string?] + // type B = Required + + const A = S.tuple(S.optionalElement(S.NumberFromString)) + const B = S.required(A) + + await Util.expectDecodeUnknownSuccess(B, ["1"], [1]) + await Util.expectDecodeUnknownFailure( + B, + [], + `readonly [NumberFromString] └─ [0] └─ is missing` - ) - }) + ) + }) - it("tuple/ e e?", async () => { - const schema = S.required(S.tuple(S.NumberFromString, S.optionalElement(S.string))) + it("e e?", async () => { + // type A = readonly [number, string?] + // type B = Required - await Util.expectDecodeUnknownSuccess(schema, ["0", ""], [0, ""]) - await Util.expectDecodeUnknownFailure( - schema, - ["0"], - `readonly [NumberFromString, string] + const A = S.tuple(S.NumberFromString, S.optionalElement(S.string)) + const B = S.required(A) + + await Util.expectDecodeUnknownSuccess(B, ["0", ""], [0, ""]) + await Util.expectDecodeUnknownFailure( + B, + ["0"], + `readonly [NumberFromString, string] └─ [1] └─ is missing` - ) - }) + ) + }) - it("tuple/ e r e", async () => { - // type A = readonly [string, ...Array, boolean] - // type B = Required // [string, ...(number | boolean)[], number | boolean] + it("e r e", async () => { + // type A = readonly [string, ...Array, boolean] + // type B = Required // readonly [string, ...number[], boolean] - const schema = S.required(S.tuple([S.string], S.number, S.boolean)) + const A = S.tuple([S.string], S.number, S.boolean) + const B = S.required(A) - await Util.expectDecodeUnknownSuccess(schema, ["", 0], ["", 0]) - await Util.expectDecodeUnknownSuccess(schema, ["", true], ["", true]) - await Util.expectDecodeUnknownSuccess(schema, ["", true, 0], ["", true, 0]) - await Util.expectDecodeUnknownSuccess(schema, ["", 0, true], ["", 0, true]) + await Util.expectDecodeUnknownSuccess(B, ["", true], ["", true]) + await Util.expectDecodeUnknownSuccess(B, ["", 0, true]) + await Util.expectDecodeUnknownSuccess(B, ["", 0, 1, true]) - await Util.expectDecodeUnknownFailure( - schema, - [], - `readonly [string, ...(number | boolean)[], number | boolean] + await Util.expectDecodeUnknownFailure( + B, + [], + `readonly [string, ...number[], boolean] └─ [0] └─ is missing` - ) - await Util.expectDecodeUnknownFailure( - schema, - [""], - `readonly [string, ...(number | boolean)[], number | boolean] + ) + await Util.expectDecodeUnknownFailure( + B, + [""], + `readonly [string, ...number[], boolean] └─ [1] └─ is missing` - ) - }) + ) + }) - it("tuple/ e r 2e", async () => { - // type A = readonly [string, ...Array, boolean, boolean] - // type B = Required // [string, ...(number | boolean)[], number | boolean, number | boolean] + it("e r e e", async () => { + // type A = readonly [string, ...Array, boolean, boolean] + // type B = Required // readonly [string, ...number[], boolean, boolean] - const schema = S.required( - S.tuple([S.string], S.number, S.boolean, S.boolean) - ) + const A = S.tuple([S.string], S.number, S.boolean, S.boolean) + const B = S.required(A) - await Util.expectDecodeUnknownSuccess(schema, ["", 0, true]) - await Util.expectDecodeUnknownSuccess(schema, ["", 0, true, false]) - await Util.expectDecodeUnknownSuccess(schema, ["", 0, 1, 2, 3, true, false]) + await Util.expectDecodeUnknownSuccess(B, ["", 0, true, false]) + await Util.expectDecodeUnknownSuccess(B, ["", 0, 1, 2, 3, true, false]) - await Util.expectDecodeUnknownFailure( - schema, - [], - `readonly [string, ...(number | boolean)[], number | boolean, number | boolean] + await Util.expectDecodeUnknownFailure( + B, + [], + `readonly [string, ...number[], boolean, boolean] └─ [0] └─ is missing` - ) - await Util.expectDecodeUnknownFailure( - schema, - [""], - `readonly [string, ...(number | boolean)[], number | boolean, number | boolean] + ) + await Util.expectDecodeUnknownFailure( + B, + [""], + `readonly [string, ...number[], boolean, boolean] └─ [1] └─ is missing` - ) - await Util.expectDecodeUnknownFailure( - schema, - ["", true], - `readonly [string, ...(number | boolean)[], number | boolean, number | boolean] + ) + await Util.expectDecodeUnknownFailure( + B, + ["", true], + `readonly [string, ...number[], boolean, boolean] └─ [2] └─ is missing` - ) - await Util.expectDecodeUnknownFailure( - schema, - ["", 0, "a"], - `readonly [string, ...(number | boolean)[], number | boolean, number | boolean] -└─ [2] - └─ number | boolean - ├─ Union member - │ └─ Expected a number, actual "a" - └─ Union member - └─ Expected a boolean, actual "a"` - ) + ) + await Util.expectDecodeUnknownFailure( + B, + ["", 0, true], + `readonly [string, ...number[], boolean, boolean] +└─ [1] + └─ Expected a boolean, actual 0` + ) + }) }) it("union", async () => {