Enforcing distinct array elements in TypeScript 4.1

Anders Hejlsberg has been on a hot streak recently, implementing several exciting features for the upcoming TypeScript 4.1 release. One of these is recursive conditional types, which I coincidentally found a use for in a personal project.

This project required a function that receives a range of keyword arguments that can appear in any order, but must be distinct from one another:

myFunction("three"); // valid
myFunction("two", "one"); // valid
myFunction("two", "one", "three"); // valid
myFunction("two", "two"); // invalid
myFunction("two", "two", "three"); // invalid

(This is not a design of my choosing, it’s a weird API compatibility issue…)

I didn’t get any useful search hits for variants of “enforce distinct array elements in TypeScript”, since most of the time people are looking for a runtime solution to this problem, so I thought I would document my solution.

type DistinctArray<T extends unknown[], U = T> = T extends []
  ? U
  : T extends [head: infer Head, ...rest: infer Tail]
  ? Head extends Tail[number]
    ? never
    : DistinctArray<Tail, U>
  : never;

The DistinctArray generic receives one type parameter for the array, and has a second type parameter that keeps track of the input array so that recursively instantiated types can resolve it.

To constrain the arguments of a function, you could use it like this:

type MyFuncKeyword = "one" | "two" | "three";
type MyFuncArguments = [
  MyFuncKeyword,
  MyFuncKeyword?,
  MyFuncKeyword?
];

function myFunction<T extends MyFuncArguments>(
  ...args: DistinctArray<T>
): void {};

myFunction("one", "two"); // OK
myFunction("one", "one"); // TS2345 error

Errors will have a vague “Argument of type ‘string’ is not assignable to parameter of type ‘never’” using the canonical never type for failure modes. You could resolve a private string enum enum DistinctArguments { _ = "" } instead to give users a hint, since they won’t be able to accidentally satisfy it with their own function declaration.

Let me know if you think of any other uses for this type!

rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora