I came across the following typescript challenge:
Unable to figure out the solution myself I searched for the most popular solution:
type Path<T> = T extends Record<PropertyKey, unknown>
? {
[P in keyof T]: [P, ...Path<T[P]>] | [P];
}[keyof T]
: never;
This works perfectly but it doesn't make sense to me. What troubles me here is that if we test the solution with an input like this:
type PathTest2 = Path<{ bar: { a: string }; baz: { b: number; c: number } }>;
we get this:
['bar'] | ['baz'] | ['bar', 'a'] | ['baz', 'b'] | ['baz', 'c']
...and I'm struggling to see how distributivity works after we get at this point:
{ baz: ['baz', ...['b'] | ['c']] | ['baz'] }
I'd get it if we passed a union to Path
but that doesn't seems to be the case here. Any clues on what's going on?
Many (but not all) type operators in TypeScript are naturally distributive over unions, since many operations are naturally covariant (see Difference between Variance, Covariance, Contravariance, Bivariance and Invariance in TypeScript).
For example, indexed access types are distributive over unions in the key (e.g., T[K1 | K2]
is T[K1] | T[K2]
). There are also distributive conditional types.
It turns out that variadic tuple types are also distributive over unions in the array/tuple type being spread. So if you have [A, B, C, ...(T1 | T2), X, Y, Z]
, this is transformed into [A, B, C, ...T1, X, Y, Z] | [A, B, C, ...T2, X, Y, Z]
. This is documented in microsoft/TypeScript#39094, the pull request that implemented variadic tuple types:
Instantiation of a tuple type with a variadic element
...T
depends on the type argument provided forT
[...] When the type argument forT
is a union type, the union is spread over the tuple type. For example,[A, ...T, B]
instantiated withX | Y | Z
as the type argument forT
yields a union of instantiations of[A, ...T, B]
withX
,Y
andZ
as the type argument forT
respectively.
So ['baz', ...['b'] | ['c']]
is automatically transformed into ['baz', 'b'] | ['baz', 'c']
.