Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Typescript: tuple with no duplicates

Problem definition

Assume we have a React component C that accepts properties Props. Props have a field named edges. Edges are defined as a tuple of length 1-4 composed of string literals top, bottom, left, right.

Task: restrict the edges param to a tuple with no duplicates.

E.g.:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

This should compile fine:

<C edges={['top', 'bottom']} />

while this should fail:

<C  edges={['top', 'top']} />

What I have so far


// Our domain types
type Top = 'top';
type Bottom = 'bottom';
type Left = 'left';
type Right = 'right';
type Edge = Top | Bottom | Left | Right;

// A helper types that determines if a certain tuple contains duplicate values
type HasDuplicate<TUPLE> = TUPLE extends [infer FIRST, infer SECOND]
    ? FIRST extends SECOND
        ? SECOND extends FIRST
            ? true
            : false
        : false
    : TUPLE extends [first: infer FIRST, ...rest: infer REST]
    ? Contains<FIRST, REST> extends true
        ? true
        : HasDuplicate<REST>
    : never;

// Just some helper type for convenience
type Contains<X, TUPLE> = TUPLE extends [infer A]
    ? X extends A
        ? A extends X
            ? true
            : false
        : false
    : TUPLE extends [a: infer A, ...rest: infer REST]
    ? X extends A
        ? A extends X
            ? true
            : Contains<X, REST>
        : Contains<X, REST>
    : never;

With the above I can already get this:

type DoesNotHaveDuplicates = HasDuplicate<[1, 2, 3]>; // === false
type DoesHaveDuplicates = HasDuplicate<[1, 0, 2, 1]>; // === true

Where I am stuck

Let’s say we have a component C:


// For simple testing purposes, case of a 3-value tuple
type MockType<ARG> = ARG extends [infer T1, infer T2, infer T3]
    ? HasDuplicate<[T1, T2, T3]> extends true
        ? never
        : [T1, T2, T3]
    : never;

interface Props<T> {
    edges: MockType<T>;
}

function C<T extends Edge[]>(props: Props<T>) {
    return null;
}

The above works but only like this:

// this compiles:
<C<[Top, Left, Right]> edges={['top', 'left', 'right']} />

// this does not (as expected):
<C<[Top, Left, Left]> edges={['top', 'left', 'left']} />

What I cannot figure out is how to get rid of the generics in component instantiation and make typescript deduce the types at compile time based on the value provided to the edges property.

>Solution :

I don’t really see the point of MockType. So let’s get rid of it.

Instead, use a conditional to check if HasDuplicate<T> is false. If it is, we can set the type of edges to be [...T].

interface Props<T extends Edge[]> {
    edges: HasDuplicate<T> extends false ? [...T] : never;
}

The variadic tuple syntax is important here as it hints to the compiler that we want to infer T as a tuple. Otherwise T will be inferred to be an array with a union of Edge as its type.

// these compile
const a = (
  <>
    <C<[Top, Left, Right]> edges={['top', 'left', 'right']} />
    <C edges={['top', 'left', 'right']} />
  </>
)

// these do not compile
const b = (
  <>
    <C<[Top, Left, Left]> edges={['top', 'left', 'left']} />
    <C edges={['top', 'left', 'left']} />
  </>
)

One a site note: We can also simplify your HasDuplicate type a bit.

type HasDuplicate<TUPLE extends any[]> = 
  TUPLE extends [infer L, ...infer R]
    ? L extends R[number]
      ? true
      : HasDuplicate<R>
    : false

Playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading