Skip to content

Latest commit

 

History

History
189 lines (136 loc) · 8.35 KB

control-distribution.md

File metadata and controls

189 lines (136 loc) · 8.35 KB

Item 53: Know How to Control the Distribution of Unions over Conditional Types

Things to Remember

  • Think about whether you want unions to distribute over your conditional types.
  • Know how to enable or disable distribution by adding conditions or by wrapping conditions in one-tuples.
  • Be aware of the surprising behavior of boolean and never types when they distribute over unions.

Code Samples

declare function double<T extends number | string>(
  x: T
): T extends string ? string : number;

const num = double(12);
//    ^? const num: number
const str = double('x');
//    ^? const str: string

declare let numOrStr: number | string;
const either = double(numOrStr);
//    ^? const either: number | string

💻 playground


type Comparable<T> =
    T extends Date ? Date | number:
    T extends number ? number :
    T extends string ? string :
    never;

declare function isLessThan<T>(a: T, b: Comparable<T>): boolean;

💻 playground


isLessThan(new Date(), new Date());  // ok
isLessThan(new Date(), Date.now());  // ok, Date/number comparison allowed
isLessThan(12, 23);  // ok
isLessThan('A', 'B');  // ok
isLessThan(12, 'B');
//             ~~~ Argument of type 'string' is not assignable to parameter
//                 of type 'number'.

💻 playground


let dateOrStr = Math.random() < 0.5 ? new Date() : 'A';
//  ^? let dateOrStr: Date | string
isLessThan(dateOrStr, 'B')  // ok, but should be an error

💻 playground


type Comparable<T> =
    [T] extends [Date] ? Date | number:
    [T] extends [number] ? number :
    [T] extends [string] ? string :
    never;

💻 playground


isLessThan(new Date(), new Date());  // ok
isLessThan(new Date(), Date.now());  // ok, Date/number comparison allowed
isLessThan(12, 23);  // ok
isLessThan('A', 'B');  // ok
isLessThan(12, 'B');
//             ~~~ Argument of type 'string' is not assignable to parameter
//                 of type 'number'.
isLessThan(dateOrStr, 'B');
//                    ~~~ Argument of type 'string' is not assignable to
//                        parameter of type 'never'.

💻 playground


type NTuple<T, N extends number> = NTupleHelp<T, N, []>;

type NTupleHelp<T, N extends number, Acc extends T[]> =
  Acc['length'] extends N
  ? Acc
  : NTupleHelp<T, N, [T, ...Acc]>;

💻 playground


type PairOfStrings = NTuple<string, 2>;
//   ^? type PairOfStrings = [string, string]
type TripleOfNumbers = NTuple<number, 3>;
//   ^? type TripleOfNumbers = [number, number, number]

💻 playground


type PairOrTriple = NTuple<bigint, 2 | 3>;
//   ^? type PairOrTriple = [bigint, bigint]

💻 playground


type NTuple<T, N extends number> =
    N extends number
    ? NTupleHelp<T, N, []>
    : never;

💻 playground


type PairOrTriple = NTuple<bigint, 2 | 3>;
//   ^? type PairOrTriple = [bigint, bigint] | [bigint, bigint, bigint]

💻 playground


type CelebrateIfTrue<V> = V extends true ? 'Huzzah!' : never;

type Party = CelebrateIfTrue<true>;
//   ^? type Party = "Huzzah!"
type NoParty = CelebrateIfTrue<false>;
//   ^? type NoParty = never
type SurpriseParty = CelebrateIfTrue<boolean>;
//   ^? type SurpriseParty = "Huzzah!"

💻 playground


type CelebrateIfTrue<V> = [V] extends [true] ? 'Huzzah!' : never;

type SurpriseParty = CelebrateIfTrue<boolean>;
//   ^? type SurpriseParty = never

💻 playground


type AllowIn<T> = T extends {password: "open-sesame"} ? "Yes" : "No";

💻 playground


type N = AllowIn<never>;
//   ^? type N = never

💻 playground