Skip to content

Commit

Permalink
Schema: align runtime tuple behaviour to ts 5.4 (#2389)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored Mar 22, 2024
1 parent 66d55a3 commit 0e1decd
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 82 deletions.
21 changes: 21 additions & 0 deletions .changeset/proud-pandas-clap.md
Original file line number Diff line number Diff line change
@@ -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<A>; // readonly [string, ...(number | boolean)[], number | boolean]
```

to

```ts
// ts 5.4
type A = readonly [string, ...number[], boolean];
type B = Required<A>; // readonly [string, ...number[], boolean]
```
8 changes: 1 addition & 7 deletions packages/schema/src/AST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
152 changes: 77 additions & 75 deletions packages/schema/test/Schema/required.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,105 +32,107 @@ describe("Schema > required", () => {
)
})

it("tuple/ e?", async () => {
// type A = [string?]
// type B = Required<A>
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<A>

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<A>

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<number>, boolean]
// type B = Required<A> // [string, ...(number | boolean)[], number | boolean]
it("e r e", async () => {
// type A = readonly [string, ...Array<number>, boolean]
// type B = Required<A> // 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<number>, boolean, boolean]
// type B = Required<A> // [string, ...(number | boolean)[], number | boolean, number | boolean]
it("e r e e", async () => {
// type A = readonly [string, ...Array<number>, boolean, boolean]
// type B = Required<A> // 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 () => {
Expand Down

0 comments on commit 0e1decd

Please sign in to comment.