Announcing Conditional Types

Sam Zhou
Flow
3 min readMar 5, 2024

--

Conditional types allow you to choose between two different output types by inspecting an input type. The syntax (inspired by TypeScript) is similar to a ternary expression:

type T = CheckType extends ExtendsType ? TrueType : FalseType;

If CheckType is a subtype of ExtendsType, the result is TrueType, otherwise it is FalseType.

We’ve also added new built-in utility types which make use of conditional types, like ReturnType<F>, Parameters<F>, Exclude<T, U>, and Extract<T, U>.

Basic Usage

The evaluating of the following conditional type is very similar to that of ternary expression:

CheckType extends ExtendsType ? TrueType : FalseType

You perform a type-level test of whether CheckType is a subtype of ExtendsType. If that’s the case, it will be evaluated to TrueType. Otherwise, it will be evaluated to FalseType.

As part of the same project, we also support infer types. Combined with generics, we can finally write down a utility type that reliably extracts the return type of a function:

type ReturnType<T: (...args: $ReadOnlyArray<empty>) => mixed> = 
T extends (...args: $ReadOnlyArray<empty>) => infer Return
? Return
: any;

New Utility Types

Advanced utility types like conditional types are generally not useful for product code, but it is very expressive to let us create utility types in the user land. With conditional types, we can now provide the following types as Flow built-ins, without any special-cased support:

  • ReturnType<F>: Obtain the return type of a function type F. e.g. ReturnType<(string, number)=>boolean> = boolean
  • Parameters<F>: Obtain the parameters of a function type F in a tuple. e.g. Parameters<(string, number)=>boolean> = [string, number]
  • Exclude<T, U>: Exclude from T those types that are assignable to U. e.g. Exclude<1 | 2 | 3 | 4, 1 | 3> = 2 | 4
  • Extract<T, U>: Extract from T those types that are assignable to U. e.g. Extract<Car | Dog | Cat, Animal> = Dog | Cat
  • ThisParameterType<F>: Extracts the type of the this parameter of a function type F. e.g. ThisParameterType<(this: foo, bar: string) => void> = foo
  • OmitThisParameter<F>: Removes the this parameter from a function type F. e.g. OmitThisParameterType<(this: foo, bar: string) => void> = (bar: string) => void

In addition, the Exclude type is necessary to power another long requested Omit type. Combined with mapped types, we hope to help you get rid of most of the confusing $Diff and $Rest, and $ObjMap type usages.

Fixed Array.flat typing

Through the distributive property of conditional type, we are finally able to fix the typing of Array.prototype.flat. Now, the following code is correctly inferred to have type Array<number>:

[1, [2, 3], 42, [65535]].flat()

Migration

The new utility types will be better replacements for most of the existing $Call use cases. In most cases, we found that people are using $Call only for extraction purposes. e.g.

type ExtractedPart = $Call<
<T>(($ReadOnlyArray<$ReadOnly<{foo: {bar: T}}>>) => string) => T,
SomeBigImportedType,
>;

can be rewritten as the following with much clearer intention with a combination of indexed access types and some of the new utility types:

type Extracted = Parameters<SomeBigImportedType>[0][number]['foo']['bar'];

Since conditional type is much clearer than $Call, we have deprecated $Call since v0.224.0. You can turn on deprecated-type lint to find all existing uses of $Call and replace them with appropriate types.

To support the new syntax, you need to update your version of Prettier, hermes-parser Babel plugin, hermes-eslint and more. Check out the adoption section in the docs for more details.

--

--