I have following type declarations:
class MyGeneric<T> { }
type ReplaceType<T> = T extends Function ? T : MyGeneric<T> | T;
ReplaceType<T>
should resolve to either MyGeneric<T> | T
or T
depending wheter T
it is a function or not:
// Input type: string
// Expected type: string | MyGeneric<string>
// Actual type: string | MyGeneric<string>
type Test1 = ReplaceType<string>;
// Input type: () => void
// Expected type: () => void
// Actual type: () => void
type Test2 = ReplaceType<() => void>;
Unfortunately, this doesn't work correctly with boolean
and union types:
// Input type: boolean
// Expected type: boolean | MyGeneric<boolean>
// Actual type: boolean | MyGeneric<true> | MyGeneric<false>
type Test3 = ReplaceType<boolean>;
// Input type: "foo" | "bar"
// Expected type: "foo" | "bar" | MyGeneric<"foo" | "bar">
// Actual type: "foo" | "bar" | MyGeneric<"foo"> | MyGeneric<"bar">
type Test4 = ReplaceType<"foo" | "bar">;
The reason boolean
and unions are have similar behaviors is because the compiler sees boolean
as a union of the literal types true
and false
, so type boolean = true | false
(although this definition does not exist explicitly)
The reason for the behavior is that by design conditional type distribute over a union. This is the designed behavior and allows all sorts of powerful things to be implemented. You can read more on the topic here
If you don't want conditionals to distribute over the union, you can use the type in a tuple (this will prevent the behavior)
class MyGeneric<T> { }
type ReplaceType<T> = [T] extends [Function] ? T : MyGeneric<T> | T;
// Input type: string
// Expected type: string | MyGeneric<string>
// Actual type: string | MyGeneric<string>
type Test1 = ReplaceType<string>;
// Input type: () => void
// Expected type: () => void
// Actual type: () => void
type Test2 = ReplaceType<() => void>;
// Input type: boolean
// Expected type: boolean | MyGeneric<boolean>
// Actual type: boolean | MyGeneric<boolean>
type Test3 = ReplaceType<boolean>;
// Input type: "foo" | "bar"
// Expected type: "foo" | "bar" | MyGeneric<"foo" | "bar">
// Actual type: "foo" | "bar" | MyGeneric<"foo" | "bar">
type Test4 = ReplaceType<"foo" | "bar">;