4

Take the following minimal example:

type BinaryOp = 'MOV'
type UnaryOp = 'ADD' | 'SUB' | 'JRO'
const BinaryOps: BinaryOp[] = ['MOV']
const UnaryOps: UnaryOp[] = ['ADD', 'SUB', 'JRO']

type Line =
    { op: BinaryOp, a: number, b: number }
  | { op: UnaryOp, a: number }

And the following "pattern match":

switch (line.op) {
    case 'ADD':
    case 'SUB':
    case 'JRO':
        return `${line.op} ${line.a}`
    case 'MOV':
        return `${line.op} ${line.a}, ${line.b}`
}

I don't particularly like that, in order for the case to understand the op is a UnaryOp or a BinaryOp, I have to enumerate all the possibilities. Is there a compact(er) way to achieve this?

NOTE. Take into consideration that this is a simplified example, and there might be other kind of Op's.

Hugo Sereno Ferreira
  • 8,600
  • 7
  • 46
  • 92
  • This is a minimal example; `default` would work in this particular case, but it's not semantically equivalent. – Hugo Sereno Ferreira Jun 10 '18 at 01:19
  • 1
    Abandoning the `switch` statement in favour of `if` and `else if` statements that call [user-defined type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html) would be one way of solving the problem. – cartant Jun 10 '18 at 01:24
  • @cartant indeed, I just solved it that way, thanks :) Wanna answer the question so I can accept it? – Hugo Sereno Ferreira Jun 10 '18 at 01:48

1 Answers1

2

I don't believe there is any TypeScript trickiness that can be used to avoid having to enumerate all of the case labels, as the labels are required to be values - not types.

However, you could use if statements instead of the switch and you could use user-defined type guards within the if expressions.

A user-defined type guard is a function that has a return type that is a type predicate. For example, a user-defined type guard for a UnaryOp might look like this:

function isUnaryOp(op: string): op is UnaryOp {
  return op && UnaryOps.includes(op as any);
}

When used in if statements, user-defined type guards will narrow the type:

if (isUnaryOp(line.op)) {
  const op = line.op; // Inferred to be UnaryOp
} else if (isBinaryOp(line.op)) {
  const op = line.op; // Inferred to be BinaryOp
} else {
  const op = line.op; // Inferred to be never
}
cartant
  • 57,105
  • 17
  • 163
  • 197
  • 1
    Indeed, one could generalize the `isSomething` helper to `export function is(op: U, collection: Array): op is T { return collection.includes(op as T) }` – Hugo Sereno Ferreira Jun 10 '18 at 02:27
  • 2
    @HugoSerenoFerreira might I also suggest a method of ensuring the union and the array stay in sync https://stackoverflow.com/questions/50771535/typescript-how-to-write-a-type-guard-to-tell-if-a-value-is-a-member-of-a-union/50771933#50771933 – Titian Cernicova-Dragomir Jun 10 '18 at 05:16