Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell TypeScript to allow multiple strings from a list, each, only once in a spread parameter?

Tags:

typescript

I have a list of paramters that can be given to a function 'one' | 'two' | 'three' | 'four' | 'five'. I want each to be used only once, like so:

function foo(...x: ('one' | 'two' | 'three' | 'four' | 'five')[]) {
  // do something...
}

foo('one', 'five', 'three', 'five'); // This works, even though I want TypeScript to say this isn't allowed.
like image 321
yaserso Avatar asked Nov 23 '20 14:11

yaserso


People also ask

Can I use multiple types of data in typescript?

Typescript 1.4 introduced Union Types so the answer now is yes, you can. function myFunc (param: string [] | boolean [] | number []): void; Using other type than the ones specified will trigger a compile-time error. If you want an array of multiple specific types, you can use Union Types for that as well:

How do I only allow specific string values in typescript?

Use a string literal type to only allow specific string values using a TypeScript type, e.g. const str: 'draft' | 'sent' = 'draft';. String literals allow us to refer to specific strings in type positions. If the specified string is not in the literal type, an error is thrown.

Do I have to declare a method as any[] in typescript?

In a method declaration in TypeScript, the parameter could be of type array of strings, booleans, or numbers. Do I have to declare it as any [] or is there a way to limit the input type as on of these three types? Typescript 1.4 introduced Union Types so the answer now is yes, you can.

Is it possible to declare multiple types for a function parameter?

From TypeScript 1.4 seems that it is possible to declare multiple possible types for a function parameter like this: class UtilsClass { selectDom (element: string | HTMLElement):Array<HTMLElement> { //Here will come the "magic-logic" } } This is because of the new TypeScript concept of "union-types". You can see more here.


1 Answers

The comment is correct: it will be nearly impossible to guarantee that only unique arrays will make it through, so no matter what you should have runtime code in place to de-duplicate any array passed in no matter what you do with the typing.

Still, you might want the typing to give users hints that you should only pass in each parameter type at most once. This is possible and not even completely crazy with TypeScript 4.1's support for recursive conditional types:

type UniqueFrom<T extends any[], U> =
  U[] extends T ? T :
  T extends [infer F, ...infer R] ?
  [U, ...UniqueFrom<R, Exclude<U, F>>] : []

type Vals = 'one' | 'two' | 'three' | 'four' | 'five';

function foo<T extends Vals[]>(...t: UniqueFrom<T, Vals>) { }

The idea is that UniqueFrom<T, Vals> should produce a tuple of the same length as T, showing which elements from Vals are still available at each step as you walk through T from left to right. for example:

type U = UniqueFrom<["one", "two", "three"], Vals>;
// [Vals, "two" | "three" | "four" | "five", "three" | "four" | "five"]

This says that the first element of T can be anything from Vals; once the first element is chosen to be "one", now the next element can be anything from Vals except "one". When the second element is chosen to be "two", now the last element can be anything from "three", "four", or "five".

As long as T is assignable to UniqueFrom<T, Vals>, then the compiler will accept a tuple of type T as a rest argument to foo(). Let's see if it works:

foo("one", "two", "three", "four", "five"); // okay
foo("two", "one", "six", "five"); // error! 
// -------------> ~~~~~
// type '"six"' is not assignable to type 'Vals'
foo("one", "two", "three", "two", "five"); // error!
// ----------------------> ~~~~~
// type '"two"' is not assignable to type '"four" | "five"'

This looks reasonable to me. The compiler complains about a second parameter named "two", and tells you that it was expecting either "four" or "five" there.

Playground link to code

like image 144
jcalz Avatar answered Sep 29 '22 10:09

jcalz