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 () => {