Skip to content

Commit 0e9be7c

Browse files
authored
Improve inference for intersection, tuple and union structs (ianstormtaylor#947)
1 parent 7b7821a commit 0e9be7c

File tree

5 files changed

+332
-158
lines changed

5 files changed

+332
-158
lines changed

src/structs/types.ts

+14-155
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Infer, Struct } from '../struct'
22
import { define } from './utilities'
33
import {
4-
TupleSchema,
54
ObjectSchema,
65
ObjectType,
76
print,
87
run,
98
isObject,
9+
AnyStruct,
10+
InferStructTuple,
11+
UnionToIntersection,
1012
} from '../utils'
1113

1214
/**
@@ -164,59 +166,9 @@ export function integer(): Struct<number, null> {
164166
* Ensure that a value matches all of a set of types.
165167
*/
166168

167-
export function intersection<A>(Structs: TupleSchema<[A]>): Struct<A, null>
168-
export function intersection<A, B>(
169-
Structs: TupleSchema<[A, B]>
170-
): Struct<A & B, null>
171-
export function intersection<A, B, C>(
172-
Structs: TupleSchema<[A, B, C]>
173-
): Struct<A & B & C, null>
174-
export function intersection<A, B, C, D>(
175-
Structs: TupleSchema<[A, B, C, D]>
176-
): Struct<A & B & C & D, null>
177-
export function intersection<A, B, C, D, E>(
178-
Structs: TupleSchema<[A, B, C, D, E]>
179-
): Struct<A & B & C & D & E, null>
180-
export function intersection<A, B, C, D, E, F>(
181-
Structs: TupleSchema<[A, B, C, D, E, F]>
182-
): Struct<A & B & C & D & E & F, null>
183-
export function intersection<A, B, C, D, E, F, G>(
184-
Structs: TupleSchema<[A, B, C, D, E, F, G]>
185-
): Struct<A & B & C & D & E & F & G, null>
186-
export function intersection<A, B, C, D, E, F, G, H>(
187-
Structs: TupleSchema<[A, B, C, D, E, F, G, H]>
188-
): Struct<A & B & C & D & E & F & G & H, null>
189-
export function intersection<A, B, C, D, E, F, G, H, I>(
190-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I]>
191-
): Struct<A & B & C & D & E & F & G & H & I, null>
192-
export function intersection<A, B, C, D, E, F, G, H, I, J>(
193-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J]>
194-
): Struct<A & B & C & D & E & F & G & H & I & J, null>
195-
export function intersection<A, B, C, D, E, F, G, H, I, J, K>(
196-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K]>
197-
): Struct<A & B & C & D & E & F & G & H & I & J & K, null>
198-
export function intersection<A, B, C, D, E, F, G, H, I, J, K, L>(
199-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L]>
200-
): Struct<A & B & C & D & E & F & G & H & I & J & K & L, null>
201-
export function intersection<A, B, C, D, E, F, G, H, I, J, K, L, M>(
202-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M]>
203-
): Struct<A & B & C & D & E & F & G & H & I & J & K & L & M, null>
204-
export function intersection<A, B, C, D, E, F, G, H, I, J, K, L, M, N>(
205-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N]>
206-
): Struct<A & B & C & D & E & F & G & H & I & J & K & L & M & N, null>
207-
export function intersection<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
208-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O]>
209-
): Struct<A & B & C & D & E & F & G & H & I & J & K & L & M & N & O, null>
210-
export function intersection<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
211-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P]>
212-
): Struct<A & B & C & D & E & F & G & H & I & J & K & L & M & N & O & P, null>
213-
export function intersection<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
214-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q]>
215-
): Struct<
216-
A & B & C & D & E & F & G & H & I & J & K & L & M & N & O & P & Q,
217-
null
218-
>
219-
export function intersection(Structs: Array<Struct<any, any>>): any {
169+
export function intersection<A extends AnyStruct, B extends AnyStruct[]>(
170+
Structs: [A, ...B]
171+
): Struct<Infer<A> & UnionToIntersection<InferStructTuple<B>[number]>, null> {
220172
return new Struct({
221173
type: 'intersection',
222174
schema: null,
@@ -476,65 +428,20 @@ export function string(): Struct<string, null> {
476428
* elements is of a specific type.
477429
*/
478430

479-
export function tuple<A>(Structs: TupleSchema<[A]>): Struct<[A], null>
480-
export function tuple<A, B>(Structs: TupleSchema<[A, B]>): Struct<[A, B], null>
481-
export function tuple<A, B, C>(
482-
Structs: TupleSchema<[A, B, C]>
483-
): Struct<[A, B, C], null>
484-
export function tuple<A, B, C, D>(
485-
Structs: TupleSchema<[A, B, C, D]>
486-
): Struct<[A, B, C, D], null>
487-
export function tuple<A, B, C, D, E>(
488-
Structs: TupleSchema<[A, B, C, D, E]>
489-
): Struct<[A, B, C, D, E], null>
490-
export function tuple<A, B, C, D, E, F>(
491-
Structs: TupleSchema<[A, B, C, D, E, F]>
492-
): Struct<[A, B, C, D, E, F], null>
493-
export function tuple<A, B, C, D, E, F, G>(
494-
Structs: TupleSchema<[A, B, C, D, E, F, G]>
495-
): Struct<[A, B, C, D, E, F, G], null>
496-
export function tuple<A, B, C, D, E, F, G, H>(
497-
Structs: TupleSchema<[A, B, C, D, E, F, G, H]>
498-
): Struct<[A, B, C, D, E, F, G, H], null>
499-
export function tuple<A, B, C, D, E, F, G, H, I>(
500-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I]>
501-
): Struct<[A, B, C, D, E, F, G, H, I], null>
502-
export function tuple<A, B, C, D, E, F, G, H, I, J>(
503-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J]>
504-
): Struct<[A, B, C, D, E, F, G, H, I, J], null>
505-
export function tuple<A, B, C, D, E, F, G, H, I, J, K>(
506-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K]>
507-
): Struct<[A, B, C, D, E, F, G, H, I, J, K], null>
508-
export function tuple<A, B, C, D, E, F, G, H, I, J, K, L>(
509-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L]>
510-
): Struct<[A, B, C, D, E, F, G, H, I, J, K, L], null>
511-
export function tuple<A, B, C, D, E, F, G, H, I, J, K, L, M>(
512-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M]>
513-
): Struct<[A, B, C, D, E, F, G, H, I, J, K, L, M], null>
514-
export function tuple<A, B, C, D, E, F, G, H, I, J, K, L, M, N>(
515-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N]>
516-
): Struct<[A, B, C, D, E, F, G, H, I, J, K, L, M, N], null>
517-
export function tuple<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
518-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O]>
519-
): Struct<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O], null>
520-
export function tuple<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
521-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P]>
522-
): Struct<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P], null>
523-
export function tuple<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
524-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q]>
525-
): Struct<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q], null>
526-
export function tuple(Elements: Struct<any>[]): any {
431+
export function tuple<A extends AnyStruct, B extends AnyStruct[]>(
432+
Structs: [A, ...B]
433+
): Struct<[Infer<A>, ...InferStructTuple<B>], null> {
527434
const Never = never()
528435

529436
return new Struct({
530437
type: 'tuple',
531438
schema: null,
532439
*entries(value) {
533440
if (Array.isArray(value)) {
534-
const length = Math.max(Elements.length, value.length)
441+
const length = Math.max(Structs.length, value.length)
535442

536443
for (let i = 0; i < length; i++) {
537-
yield [i, value[i], Elements[i] || Never]
444+
yield [i, value[i], Structs[i] || Never]
538445
}
539446
}
540447
},
@@ -580,57 +487,9 @@ export function type<S extends ObjectSchema>(
580487
* Ensure that a value matches one of a set of types.
581488
*/
582489

583-
export function union<A>(Structs: TupleSchema<[A]>): Struct<A, null>
584-
export function union<A, B>(Structs: TupleSchema<[A, B]>): Struct<A | B, null>
585-
export function union<A, B, C>(
586-
Structs: TupleSchema<[A, B, C]>
587-
): Struct<A | B | C, null>
588-
export function union<A, B, C, D>(
589-
Structs: TupleSchema<[A, B, C, D]>
590-
): Struct<A | B | C | D, null>
591-
export function union<A, B, C, D, E>(
592-
Structs: TupleSchema<[A, B, C, D, E]>
593-
): Struct<A | B | C | D | E, null>
594-
export function union<A, B, C, D, E, F>(
595-
Structs: TupleSchema<[A, B, C, D, E, F]>
596-
): Struct<A | B | C | D | E | F, null>
597-
export function union<A, B, C, D, E, F, G>(
598-
Structs: TupleSchema<[A, B, C, D, E, F, G]>
599-
): Struct<A | B | C | D | E | F | G, null>
600-
export function union<A, B, C, D, E, F, G, H>(
601-
Structs: TupleSchema<[A, B, C, D, E, F, G, H]>
602-
): Struct<A | B | C | D | E | F | G | H, null>
603-
export function union<A, B, C, D, E, F, G, H, I>(
604-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I]>
605-
): Struct<A | B | C | D | E | F | G | H | I, null>
606-
export function union<A, B, C, D, E, F, G, H, I, J>(
607-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J]>
608-
): Struct<A | B | C | D | E | F | G | H | I | J, null>
609-
export function union<A, B, C, D, E, F, G, H, I, J, K>(
610-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K]>
611-
): Struct<A | B | C | D | E | F | G | H | I | J | K, null>
612-
export function union<A, B, C, D, E, F, G, H, I, J, K, L>(
613-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L]>
614-
): Struct<A | B | C | D | E | F | G | H | I | J | K | L, null>
615-
export function union<A, B, C, D, E, F, G, H, I, J, K, L, M>(
616-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M]>
617-
): Struct<A | B | C | D | E | F | G | H | I | J | K | L | M, null>
618-
export function union<A, B, C, D, E, F, G, H, I, J, K, L, M, N>(
619-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N]>
620-
): Struct<A | B | C | D | E | F | G | H | I | J | K | L | M | N, null>
621-
export function union<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
622-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O]>
623-
): Struct<A | B | C | D | E | F | G | H | I | J | K | L | M | N | O, null>
624-
export function union<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
625-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P]>
626-
): Struct<A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P, null>
627-
export function union<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
628-
Structs: TupleSchema<[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q]>
629-
): Struct<
630-
A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q,
631-
null
632-
>
633-
export function union(Structs: Struct<any>[]): any {
490+
export function union<A extends AnyStruct, B extends AnyStruct[]>(
491+
Structs: [A, ...B]
492+
): Struct<Infer<A> | InferStructTuple<B>[number], null> {
634493
const description = Structs.map((s) => s.type).join(' | ')
635494
return new Struct({
636495
type: 'union',

src/utils.ts

+40
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ export function* run<T, S>(
193193
}
194194
}
195195

196+
/**
197+
* Convert a union of type to an intersection.
198+
*/
199+
200+
export type UnionToIntersection<U> = (
201+
U extends any ? (arg: U) => any : never
202+
) extends (arg: infer I) => void
203+
? I
204+
: never
205+
196206
/**
197207
* Assign properties from one type to another, overwriting existing.
198208
*/
@@ -366,3 +376,33 @@ export type StructSchema<T> = [T] extends [string | undefined]
366376
*/
367377

368378
export type TupleSchema<T> = { [K in keyof T]: Struct<T[K]> }
379+
380+
/**
381+
* Shorthand type for matching any `Struct`.
382+
*/
383+
384+
export type AnyStruct = Struct<any, any>
385+
386+
/**
387+
* Infer a tuple of types from a tuple of `Struct`s.
388+
*
389+
* This is used to recursively retrieve the type from `union` `intersection` and
390+
* `tuple` structs.
391+
*/
392+
393+
export type InferStructTuple<
394+
Tuple extends AnyStruct[],
395+
Length extends number = Tuple['length']
396+
> = Length extends Length
397+
? number extends Length
398+
? Tuple
399+
: _InferTuple<Tuple, Length, []>
400+
: never
401+
type _InferTuple<
402+
Tuple extends AnyStruct[],
403+
Length extends number,
404+
Accumulated extends unknown[],
405+
Index extends number = Accumulated['length']
406+
> = Index extends Length
407+
? Accumulated
408+
: _InferTuple<Tuple, Length, [...Accumulated, Infer<Tuple[Index]>]>

test/typings/intersection.ts

+90-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,96 @@
11
import { assert, intersection, object, string } from '../..'
22
import { test } from '..'
33

4-
test<{ a: string } & { b: string }>((x) => {
4+
test<{ a: string; b: string }>((x) => {
55
assert(x, intersection([object({ a: string() }), object({ b: string() })]))
66
return x
77
})
8+
9+
// Maximum call stack of 39 items
10+
test<{
11+
a1: string
12+
a2: string
13+
a3: string
14+
a4: string
15+
a5: string
16+
a6: string
17+
a7: string
18+
a8: string
19+
a9: string
20+
a10: string
21+
a11: string
22+
a12: string
23+
a13: string
24+
a14: string
25+
a15: string
26+
a16: string
27+
a17: string
28+
a18: string
29+
a19: string
30+
a20: string
31+
a21: string
32+
a22: string
33+
a23: string
34+
a24: string
35+
a25: string
36+
a26: string
37+
a27: string
38+
a28: string
39+
a29: string
40+
a30: string
41+
a31: string
42+
a32: string
43+
a33: string
44+
a34: string
45+
a35: string
46+
a36: string
47+
a37: string
48+
a38: string
49+
a39: string
50+
}>((x) => {
51+
assert(
52+
x,
53+
intersection([
54+
object({ a1: string() }),
55+
object({ a2: string() }),
56+
object({ a3: string() }),
57+
object({ a4: string() }),
58+
object({ a5: string() }),
59+
object({ a6: string() }),
60+
object({ a7: string() }),
61+
object({ a8: string() }),
62+
object({ a9: string() }),
63+
object({ a10: string() }),
64+
object({ a11: string() }),
65+
object({ a12: string() }),
66+
object({ a13: string() }),
67+
object({ a14: string() }),
68+
object({ a15: string() }),
69+
object({ a16: string() }),
70+
object({ a17: string() }),
71+
object({ a18: string() }),
72+
object({ a19: string() }),
73+
object({ a20: string() }),
74+
object({ a21: string() }),
75+
object({ a22: string() }),
76+
object({ a23: string() }),
77+
object({ a24: string() }),
78+
object({ a25: string() }),
79+
object({ a26: string() }),
80+
object({ a27: string() }),
81+
object({ a28: string() }),
82+
object({ a29: string() }),
83+
object({ a30: string() }),
84+
object({ a31: string() }),
85+
object({ a32: string() }),
86+
object({ a33: string() }),
87+
object({ a34: string() }),
88+
object({ a35: string() }),
89+
object({ a36: string() }),
90+
object({ a37: string() }),
91+
object({ a38: string() }),
92+
object({ a39: string() }),
93+
])
94+
)
95+
return x
96+
})

0 commit comments

Comments
 (0)