I didn't know how to formulate my question properly, so I'll give an example.
type ValueType = "NUM" | "STR";
type TypeOf<T>
= T extends "NUM" ? number
: T extends "STR" ? string
: never;
interface TypedValue<T = ValueType> {
type: T;
data: TypeOf<T>;
}
// Compiles, as intended
const test1: TypedValue = { type: "NUM", data: 123 };
// Does not compile, as intended
const test2: TypedValue<"NUM"> = { type: "NUM", data: "123" };
// Should not compile, but does...
const test3: TypedValue = { type: "NUM", data: "123" };
It seems that Typescript generates many concrete types for the interface TypedValue:
so
interface TypedValue<T = ValueType, D = TypeOf<T>>
corresponds to
interface TypedValue<"NUM", number>
interface TypedValue<"NUM", string>
interface TypedValue<"NUM", never>
interface TypedValue<"STR", number>
interface TypedValue<"STR", string>
interface TypedValue<"STR", never>
and maybe more, while I actually want this generic type to correspond to just
interface TypedValue<"NUM", number>
interface TypedValue<"STR", string>
How can I avoid this type distribution, e.g. how can I tie one type parameter to another type parameter in typescript?
I know about the trick to suppress type distribution using
type TypeOf<T>
= [T] extends ["NUM"] ? number
: [T] extends ["STR"] ? string
: never;
But I can't seem solve the puzzle myself, and I really want to dig deeper in this magical type system, so any help is welcome :) Pretty sure jcalz knows how to tackle this ;)
EDIT It finally clicked after Titian Cernicova-Dragomir answer! Personally I understand the solution better with the following code snippet:
type Pairs1<T> = [T, T];
type Pairs2<T> = T extends (infer X) ? [X, X] : never;
type P1 = Pairs1<"A" | "B">; // => ["A" | "B", "A" | "B"]
type P2 = Pairs2<"A" | "B">; // => ["A", "A"] | ["B" | "B"]
What seems to happen, the Typescript compiler will check T extends (infer X) for each union member "A"|"B", which always succeeds, but it now binds the matching type variable to a non-union type variable X. And the infer X is actually not needed, but it helped me to understand it better.
Infinite gratitude, I've been struggling with this for a long time.
So now I finally understand the following excerpt from the Typescript manual:
In instantiations of a distributive conditional type T extends U ? X : Y, references to T within the conditional type are resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e. T is considered assignable to U within X).