type branching
Introduced in v8, type branching is a way to finely control output of the type utility.
Using IsNever as an example, it supports 4 branches:
type R = IsNever<Input, { $any: ..., $unknown: ..., $then: ..., $else: ...,}>These branches allow you to control the behavior on those specific cases:
type R = IsNever<any> // falsetype R = IsNever<any, { $any: 'any' }> // 'any'
type R = IsNever<unknown> // falsetype R = IsNever<unknown, { $unknown: 'unknown' }> // 'unknown'
type R = IsNever<never> // truetype R = IsNever<never, { $then: 'never' }> // 'never'
type R = IsNever<123> // falsetype R = IsNever<123, { $else: 'else' }> // 'else'Without this, you would have to write conditional type to achieve the same result:
type R = IsAny<any> extends true ? 'any' : IsNever<any>type R = IsUnknown<unknown> extends true ? 'unknown' : IsNever<unknown>type R = IsNever<never> extends true ? 'never' : falsetype R = IsNever<123> extends true ? true : 'else'It could also get more complicated with the special types (any, unknown, never, void),
as well as union and intersection types.
Branch Selectors
Section titled “Branch Selectors”Besides customizing the output, type branching is also useful for type level programming.
When you are using some type utils within your type, you may want to do different things based on the type of the input.
In this case, only using the construct above may not be enough, because the branching can be influenced by the type of the input.
Instead, you can use the branch selectors:
type YourType<T> = IsNever<T, IsNever.$Branch> extends infer B ? B extends $Any ? ... : B extends $Unknown ? ... : B extends $Then ? ... : B extends $Else ? ...: neverThe $Options in IsNever<T, $Options> defines the options, branches, and their default values.
The IsNever.$Branch replaces the branches with the branch selectors.
Each branch selector is a named interface that extends $Branch, and carries its
branch name in the ~type-plus/branch key:
interface $Then extends $Branch<'$then'> {}
type R = $Then['~type-plus/branch'] // '$then'type R = $Then[$Branch.$Key] // '$then'$Branch.$Key is that key. Prefer it in type-level code over spelling the key out,
so your code follows the key if it is renamed.
A selector is not a string, so it cannot be mistaken for a real result
(IsString<$Then> is false), and selectors with different names are distinct.
Errors and hovers print it by name, as $Then.
Do not treat a selector as a string: $Then extends string is false.
To read its name, use $B[$Branch.$Key], or match the selector with extends.
You can still provide options using intersection:
type YourType<T> = IsNever<T, IsNever.$Branch & { selection: 'filter' }> extends infer B ...$ResolveBranch
Section titled “$ResolveBranch”$ResolveBranch is a utility to resolve the branch selector.
You may notice that in the example above,
there could be a overlap between the branches:
type R = IsNever<any, { $any: 'any', $else: 'else' }> // 'any' or `else`?Since properties in object type are not ordered,
i.e. { $any: 'any', $else: 'else' } is the same as { $else: 'else', $any: 'any' },
there needs to be a way to define and resolve the branch in a particular order.
$ResolveBranch is the type used within IsNever to handle this:
// only emphasize the relevant partstype IsNever<T, $O> = $Special<T, { $any: $ResolveBranch<..., [$Any, $Else]>, ...}>The $ResolveBranch will check the result and return the first matching branch selector in the array.
You can use it to develop your own type utils with type branching support.
Branch options
Section titled “Branch options”To take part in branching, a type accepts a $O options parameter carrying one
property per branch. A branch property is named $<key> and is typically typed
as unknown; its key doubles as the branch identifier.
type IsAny<T, $O extends { $unknown?: unknown $never?: unknown $then?: unknown $else?: unknown}> = ...The real definition of IsAny looks different, but the shape is the same. Those
four keys are not declared by hand — each comes from the option type that owns
it: $Unknown.$Options, $Never.$Options and $Selection.Options. When you
write a type of your own with branching support, compose the option types for
the branches you want rather than restating their keys.
Declare the composed options as an interface rather than a type alias. An
interface keeps its name, so an error on a bad option names
YourType.$Options instead of expanding every part, and it costs the compiler
slightly less work per use:
namespace YourType { export interface $Options extends $Selection.Options, $InputOptions<$Any | $Unknown> {}}Constrain $O with $StrictOptions so a misspelled key is an error, as it is for the type-plus types:
type YourType<T, $O extends $StrictOptions<$O, YourType.$Options> = {}> = ...A generic $O has to be forwarded to another type with $ForwardOptions.
See Unknown option keys and generic wrappers.
$Selection also holds ready-made option objects that pick a branch style:
$Selection.Predicate returns true or false, which is what a type does
when $O is {}; $Selection.Filter<T> returns T or never; and
$Selection.Branch returns $Then or $Else. $Selection.$BaseOptions is
the $then/$else half of $Selection.Options, for a type that has no
selection option, such as And.
Two more help you build the inverse of an existing type.
$Selection.Flip<$O> swaps the $then and $else options, and
$Selection.Invert<B> swaps a $Then selector for $Else and back:
type IsNotBoolean<T> = IsBoolean<T, $Selection.Flip<$Selection.Predicate>>type R = $Selection.Invert<$Then> // $ElseSelection branching is the most common kind, and $Selection is named after
“selection” in the three building blocks of structured programming: sequence,
selection, and iteration.
$BranchOptions<$B> builds the options object that maps each branch to its
selector, which is how a type’s $Branch member is written:
type R = $BranchOptions<$Then | $Else> // { $then: $Then, $else: $Else }Option building blocks
Section titled “Option building blocks”$Selection.Options is one of several option types a predicate composes. Each
covers one option and comes with a Default:
$Distributiveholdsdistributive, which defaults totrue. With{ distributive: false }a predicate judges a union as a whole instead of each member, soIsObject<{} | 1, { distributive: false }>isfalserather thanboolean.$Exactholdsexact, which defaults tofalse. With{ exact: true }a predicate asks whetherTis exactly the type rather than assignable to it, soIsObject<{}, { exact: true }>isfalse.$Failholds$fail, the value a transform returns when it cannot compute a result, such asAdd<number, 1>.$Fail.$Defaultsets it tonever.
$Distributive.Parse<$O> and $Exact.Parse<$O> resolve the caller’s option
against the default, so your own type does not repeat the default value:
type R = $Distributive.Parse<{}> // truetype R = $Exact.Parse<{ exact: true }, { $then: 'yes', $else: 'no' }> // 'yes'Two utilities combine option values. $ResolveOptions<[A, B, ...]> returns
the first value that is neither unknown nor undefined, which is how an
option that was left out falls back to its default. $MergeOptions<$O, $P>
merges $P over $O, so the keys in $P win:
type R = $ResolveOptions<[undefined, false]> // falsetype R = $MergeOptions<{ $any: 1, $else: 2 }, { $any: 2 }> // { $else: 2 } & { $any: 2 }Special types
Section titled “Special types”any, unknown, never and void are TypeScript’s special types — the top
and bottom of the type system, with no corresponding JavaScript value. Type-level
code usually has to treat them apart from everything else, and $Special is the
utility for that.
By default it is a predicate:
type A = $Special<any> // truetype A = $Special<unknown> // truetype A = $Special<never> // truetype A = $Special<void> // truetype A = $Special<number> // falseIt can also act as a filter:
type A = $Special<any, { selection: 'filter' }> // anytype A = $Special<number, { selection: 'filter' }> // neverMost of the time it is used with branching, to give each special type its own answer:
type A<T> = $Special<T, { $any: ..., $unknown: ..., $never: ..., $void: ..., $then: ..., $else: ...,}>Each special type has a branch selector: $Any, $Unknown, $Never and
$Void. Each also has an $Options member that declares its branch key, and a
$Branch member that sets the branch to the selector:
type R = IsAny<void, { $void: 'void' }> // 'void'type R = IsAny<void, $Void.$Branch> // $Void$NotNever is the selector IsNever returns from its filter when T is not
never. A filter usually signals a failed check with never, which is the
value IsNever is checking for:
type R = IsNever<1, { selection: 'filter' }> // $NotNeverType-level errors
Section titled “Type-level errors”A type-level computation cannot throw, so it has to return something that says
it failed. $Error<M, V = unknown> is that something — the type-level analogue
of the Error class. M is the message and V optionally carries the value
that caused it.
type YourComplexType<A, B, C> = A extends B ? ... // and many more hard work later... : $Error<'you are doing something wrong!', C>$InferError<M, T> covers the narrower case of an inference that did not go the
way you expected. T extends infer U extends V lets you constrain an inferred
type, at the cost of an extra conditional; $InferError is what you return from
its else branch.
type F<T> = T extends infer U extends V ? ...your type logic... : $InferError<'some message', T>$ErrorMessage<M> is a string type that carries an error message for a
constraint to report. $StrictOptions uses it to name a misspelled option key.
See Unknown option keys and generic wrappers.
Type functions
Section titled “Type functions”$Fn is a type function, and $Fn.Apply<F, A> calls it.
A predicate’s .$Fn, such as IsObject.$Fn<{ exact: true }>, is the predicate with its options applied.
You can pass one to Filter, Find, Some or DropMatch.
See passing predicates to collection types.
$Type and the $ prefix
Section titled “$Type and the $ prefix”Types named with a $ prefix are building blocks for writing types, not types
to reach for in application code.
$Type underpins them. It is a branded type used to define types that are
unique at the type level, and it supports both primitives and object types. For
an object type it intersects with the type you give it, so the resulting type
still exposes that type’s properties directly. It stores the type and value in
the internal properties _$type and _$value, so the type you pass in should
not declare properties by those names. Pass $O: { bare: true } if you want the
brand without the intersection.
The branch selectors do not use $Type; they extend $Branch, described in
branch selectors.