Skip to content

Commit 45971af

Browse files
committed
Fix StaticUnion regression for non-tuple Array<TSchema>
In typebox 0.34, UnionStatic used a mapped-index-signature pattern `{[K in keyof T]: Static<T[K]>}[number]` that resolved correctly for both tuple types and general `TSchema[]` arrays. In 1.x, StaticUnion was rewritten using head/tail destructuring with a `Result = never` default. Because `TSchema[]` does not match `[Left, ...Right]` (the array may be empty), `Static<TUnion<TSchema[]>>` falls through to `never`. This regresses any code that builds a union from a variable typed as a plain array: const errors = [Type.Object(...), Type.Object(...)]; const schema = Type.Union(errors); type T = Static<typeof schema>; // resolves to `never` instead of the union Fix: add an `Array<infer Item extends TSchema>` fallback that infers the element type when the input is not a tuple. Tuples still go through the fast recursive path; the new branch only fires for general arrays. Adds static tests covering: - inline tuple inference (existing behaviour) - generic TSchema[] (must not resolve to never) - narrowed Array<TLiteral<1|2|3>> (must resolve to 1|2|3)
1 parent ea23b86 commit 45971af

2 files changed

Lines changed: 41 additions & 10 deletions

File tree

src/type/types/union.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ import { type TProperties } from './properties.ts'
3939
export type StaticUnion<Stack extends string[], Direction extends StaticDirection, Context extends TProperties, This extends TProperties, Types extends TSchema[], Result extends unknown = never> = (
4040
Types extends [infer Left extends TSchema, ...infer Right extends TSchema[]]
4141
? StaticUnion<Stack, Direction, Context, This, Right, Result | StaticType<Stack, Direction, Context, This, Left>>
42-
: Result
42+
: Types extends Array<infer Item extends TSchema>
43+
? Result | StaticType<Stack, Direction, Context, This, Item>
44+
: Result
4345
)
4446
// ------------------------------------------------------------------
4547
// Type

test/typebox/static/type/union.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
1-
import { type Static, Type } from 'typebox'
1+
import { type Static, type TSchema, Type } from 'typebox'
22
import { Assert } from 'test'
33

4-
const T = Type.Union([
5-
Type.Literal(1),
6-
Type.Literal(2),
7-
Type.Literal(3)
8-
])
9-
type T = Static<typeof T>
4+
// inline tuple inference
5+
{
6+
const T = Type.Union([
7+
Type.Literal(1),
8+
Type.Literal(2),
9+
Type.Literal(3)
10+
])
11+
type T = Static<typeof T>
1012

11-
Assert.IsExtendsMutual<T, 1 | 2 | 3>(true)
12-
Assert.IsExtendsMutual<T, null>(false)
13+
Assert.IsExtendsMutual<T, 1 | 2 | 3>(true)
14+
Assert.IsExtendsMutual<T, null>(false)
15+
}
16+
// non-tuple TSchema[] should still resolve to a union (not never)
17+
{
18+
const Schemas: TSchema[] = [
19+
Type.Literal(1),
20+
Type.Literal(2),
21+
Type.Literal(3)
22+
]
23+
const T = Type.Union(Schemas)
24+
type T = Static<typeof T>
25+
26+
Assert.IsExtendsMutual<T, unknown>(true)
27+
Assert.IsExtendsNever<T>(false)
28+
}
29+
// narrowed element type via Array<TLiteral<...>> should resolve to the literal union
30+
{
31+
const Schemas: Array<ReturnType<typeof Type.Literal<1 | 2 | 3>>> = [
32+
Type.Literal(1 as const),
33+
Type.Literal(2 as const),
34+
Type.Literal(3 as const)
35+
]
36+
const T = Type.Union(Schemas)
37+
type T = Static<typeof T>
38+
39+
Assert.IsExtendsMutual<T, 1 | 2 | 3>(true)
40+
Assert.IsExtendsNever<T>(false)
41+
}

0 commit comments

Comments
 (0)