The issue you're encountering with
type ObjectAddress<T> = {
[K in keyof T]: [K, ...DataAddress<T[K]>];
}[keyof T];
stems from the [keyof T]
at the end. Your ObjectAddress<T>
is considered a distributive object type (as introduced in microsoft/TypeScript#47109) which occurs when you create a mapped type and then instantly index into it with all its keys, resulting in a union of the property value types.
However, this approach works well for "standard" object types. Yet, when T
represents an array type or a tuple type, things go awry. The mapped type {[K in keyof T]: ⋯}
only cycles through the numeric-like keys and generates another array or tuple type (as per Typescript 3.1 release notes). However, the keyof T
at the conclusion encompasses all keys of the array type T
, such as "length", "push", and more. Consequently, instead of obtaining just the union of [K, ...DataAddress<T[K]>]
for each numeric K
, you also receive a mixture of all other members like number
for length
and some function type for push
, creating confusion.
Hence, one workaround involves verifying if T
resembles an array and if so, solely employing number
for indexing rather than keyof T
. Perhaps implemented in this manner:
type ObjectAddress<T> = {
[K in keyof T]: [K, ...DataAddress<T[K]>];
}[(T extends readonly any[] ? number : unknown) & keyof T];
In this scenario, the conditional type results in number
for arraylike entities and unknown
for non-array ones. When intersecting that with keyof T
, the outcome suggests number & keyof T
(often interpreted as number
) when T
emulates an array or simply keyof T
for non-array instances.
Upon making this modification, your sample operates as intended:
type X = { x: boolean[] }
type Z = DataAddress<X>
// ^? type Z = ["x", number, false] | ["x", number, true]
Link to Playground showcasing code changes