0

Can I add a type to a literal in TypeScript? Something like the below:

type State = 'DRAFT' | 'PUBLISHED';
(['DRAFT', 'OOPS']: State[]).map(s => doSomethingWithState(s)); // Should be flagged by the compiler as 'OOPS' is not compatible with State
(['DRAFT']: State[]).map(s => doSomethingWithState(s)); // Valid
(['DRAFT', 'PUBLISHED']: State[]).map(s => doSomethingWithState(s)); // Valid

I have tried using as but this tells the compiler to treat the as as though it were of the given type, not to check that the literal is of the given type.

I could also do const foo: Type = ... but I don't need to assign this value, I just want to declare it and use it immediately.

isherwood
  • 58,414
  • 16
  • 114
  • 157
old greg
  • 799
  • 8
  • 19
  • A little confusing what you want to achieve. But if you want to add `OOPS` to `State`. You will have to it in the first line. Dynamically adding to union type is possible... but you will have to have the array containing each string literal you want in the array _before_ you create the type. See this: https://stackoverflow.com/a/55505556/298455 – Nishant May 13 '21 at 12:58
  • `'OOPS'` is not part of the union; is this intentional? – iz_ May 13 '21 at 12:58
  • Either put `OOPS` in `State` or extend `State` with a new `type` that includes it (e.g. if `State` comes from a vendor, but *your* code needs to use the new type - caveat: you cannot pass that value to the vendor's type). – crashmstr May 13 '21 at 13:00
  • I've updated the example. I thought the name 'OOPS' would make it obvious this was an intentional error to demonstrate the problem, but I guess not... – old greg May 13 '21 at 13:02
  • Did you mean somthing like [this](https://www.typescriptlang.org/play?#code/C4TwDgpgBAysCGxoF4oHIAiAlAggMQBU0oAfdABQFUAhAGQEkYAJAUQzQG4BYAKAAoA2plyFi8AM5QAxgHsAduOAAaCjQbM2YybIXAAugEoAdAFt4YPpOQA+KABMZMGSYjAAFgEs5AcwDqH9zhECEsDA25+IWx8IigJaXlFFTQAeRTyGC0E3UNTc0soG3tHZ1dPH39AhCRQ8N5eADMAVzkpYA95YqcXdy8-ALcgmvEALiGIAwBvAF8gA)? – spender May 13 '21 at 13:04
  • @spender I guess that works, but my actual use case is more complex than two elements. If I have thousands, or I build the array programmatically, can I then flag all elements as `as const`? – old greg May 13 '21 at 13:05
  • I think that to extend this to arbitrary # of items, you're probably best declaring an intermediate `const` of type `State[]` – spender May 13 '21 at 13:09
  • I think that if you have a large number of dynamic arrays, it cannot be done. See my answer below. – fog May 13 '21 at 14:47

1 Answers1

0

I think the problem is that TypeScript cannot determine at compile-time if an array with a "more generic" type, e.g., string[] contains only elements from a restricted type (like State). Such a check can only be done at runtime (except for constant arrays and that's why it works if you use const) and that's why the cast from string[] to State[] always work. For example:

type State = 'DRAFT' | 'PUBLISHED';

let a = ['DRAFT', 'DRAFT', 'PUBLISHED'];
let b: State[] = a;

The assignment to b will result in the Type 'string[]' is not assignable to type 'State[]' error. You can resolve the problem using a cast to State[] but then:

let a = ['DRAFT', 'OOOPS', 'PUBLISHED'];
let b: State[] = a as State[];

the assignment will not trigger an error: the compiler is happy to cast a ignoring its contents bacause it does not know its contents.

So, the answer to your problem is: probably it cannot be done.

fog
  • 3,266
  • 1
  • 25
  • 31