Typescript version: 5.6.2
Why the ApprovalType
was tampered into specific values of ApprovalType
if the function parameter type is a conditional type, even if I explicitly pass in the generic parameter <ApprovalType>
when calling the function?
type ApprovalType = "PENDING" | "APPROVED" | "REJECTED";
type A<T> = {
options: T[];
};
type B<T> = {
options: T[];
};
function conditionalTypeFn<T>(props: T extends string ? B<T> : A<T>) {
return props;
}
conditionalTypeFn<ApprovalType>({
/**
* ❌ Type '("PENDING" | "APPROVED" | "REJECTED")[]' is not assignable
* to type '"PENDING"[] | "APPROVED"[] | "REJECTED"[]'
*/
options: ["PENDING", "APPROVED", "REJECTED"],
});
function unionTypeFn<T>(props: A<T> | B<T>) {
return props;
}
unionTypeFn<ApprovalType>({
/* ✅ no error */
options: ["PENDING", "APPROVED", "REJECTED"],
});
By default, as explained in the documentation, conditional types behave in a distributive manner when used on a union type.
Here is a simplified example that demonstrates this:
type ApprovalType = "PENDING" | "APPROVED" | "REJECTED";
type Distributed<T> = T extends string ? T[] : never;
type ApprovalTypes = Distributed<ApprovalType>;
// type ApprovalTypes = "PENDING"[] | "APPROVED"[] | "REJECTED"[]
To avoid the distributive behavior, you can use square brackets in your conditional type. Applied to the same example, this would yield:
type ApprovalType = "PENDING" | "APPROVED" | "REJECTED";
type NonDistributed<T> = [T] extends [string] ? T[] : never;
type ApprovalTypes = NonDistributed<ApprovalType>;
// type ApprovalTypes = ("PENDING" | "APPROVED" | "REJECTED")[]
Or, applied to the code in your question:
function conditionalTypeFn<T>(props: [T] extends [string] ? B<T> : A<T>) {
return props;
}