Basically, what you're seeking is a modified version of the Extract<T, U>
utility type that retrieves any item from the union T
which serves as a superior type to U
, rather than a subclass of U
. In this way,
ExtractSubtype<keyof Foo, T>
will consist of any element within
keyof Foo
where
T
can be assigned to it, and not vice versa.
The definition of Extract
is available at this link:
type Extract<T, U> = T extends U ? T : never;
This represents a distributive conditional type that disassembles a union T
into its constituent parts, and assesses each part for assignment compatibility with U
.
An implementation of ExtractSubtype<T, U>
would look like this:
type ExtractSubtype<T, U> =
T extends any ? [U] extends [T] ? T : never : never;
In order to preserve distribution over T
, we use T extends ⋯ ? ⋯ : ⋯
. Although there isn't a need to actually 'check' T
, so we establish a condition that always evaluates as true, such as T extends any
or T extends unknown
or T extends T
. Therefore, we employ T extends any ? ⋯ : never
to distribute across T
.
The core evaluation is on [U] extends [T] ? T : never
, verifying whether the type U
can be assigned to the specific member of
T</code under consideration. The bracket notation <code>[]
is used here to disable distributivity, as we wish to maintain
U
as-is without breaking it down into individual union elements.
With this insight, we can derive:
type InferFooKey<U extends string> = ExtractSubtype<keyof Foo, U>
type TestInfer = InferFooKey<'/coffee/123/time'>;
// type TestInfer = `/coffee/${string}/time`^?
seems promising.
If the goal is to accomplish most of this in-line to avoid reusing ExtractSubtype
, then consider this approach:
type InferFooKey<U extends string, T = keyof Foo> =
T extends any ? [U] extends [T] ? T : never : never;
type TestInfer = InferFooKey<'/coffee/123/time'>;
// type TestInfer = `/coffee/${string}/time`^?
Here, I've leveraged a default generic type argument to ensure that T
remains consistent with keyof Foo
, enabling T extends any ? ⋯ : never
to operate correctly. Without this default setting, a direct check like
keyof Foo extends any ? ⋯ : never
wouldn't distribute (due to the requirement of checking a generic type parameter for distributive conditional types).
To extend distribution across unions within U
, modify the code as follows:
type InferFooKey<U extends string, T = keyof Foo> =
T extends any ? U extends T ? T : never : never;
The outcome depends on your desired behavior for InferFooKey<U>
when dealing with a union.
type Test2 = InferFooKey<'/tea/abc/cup' | '/coffee/def/time'>;
// never? since no key is assignable to that union... or
// `/tea/${string}/cup` | `/coffee/${string}/time`
Either method is suitable for the provided example scenario.
Refer to this Playground link to code