1

I'm trying to create a higher order function that allows me to create a function which confirms a type via a type guard.

type Shape = Circle | Triangle | Rectangle
enum Type { Circle, Triangle, Rectangle }

interface Circle {
  type: Type.Circle;
  diameter: number;
}

interface Triangle {
  type: Type.Triangle;
  width: number;
}

interface Rectangle {
  type: Type.Rectangle;
  width: number;
  height: number;
}

const isShape = (condition: Type) => (shape: Shape): shape is ? => shape.type == condition;

const isCircle = isShape(Type.Circle);

In the above example, I want the isCircle function to return whether the type is Circle. The ? is a placeholder since I can't get it to work.

  • Related [Mapping from discriminated union property to object type](https://stackoverflow.com/questions/47932908/mapping-from-discriminated-union-property-to-object-type). *In short, without restructuring your types, it won't be possible to give a signature to `isShape` that does what you want.* – CRice Oct 16 '18 at 22:58
  • @CRice, how would the types have to be structured in order to achieve this? – Lennard Schutter Oct 17 '18 at 01:27

2 Answers2

2

To achieve your objective, here is what your isShape function should look like:

const isShape = <T extends Shape>(condition: Type) => {
  return function(shape: T): shape is T {
    return shape.type == condition;
  };
};

const isCircle = isShape<Circle>(Type.Circle);
Akshar Patel
  • 5,377
  • 4
  • 20
  • 25
2

You need to provide a direct way for TypeScript to figure out what Shape is associated with a given Type. One way to do that is to explicitly provide a mapping between the two, which can then be used to replace your definition for Shape. So, for example:

// Instead of this:
// type Shape = Circle | Triangle | Rectangle

// Use this:
type TypeToShape = {
  [Type.Circle]: Circle,
  [Type.Triangle]: Triangle,
  [Type.Rectangle]: Rectangle,
}

// Circle | Triangle | Rectangle
type Shape = TypeToShape[keyof TypeToShape];

Then, given a specific Type, it's easy to map that to the Shape associated with it:

// Use the type map to tell typescript what shape goes with the Type K:
const isShape = <K extends Type>(condition: K) => (shape: Shape): shape is TypeToShapeMap[K] => shape.type == condition;

const isCircle = isShape(Type.Circle); // return type: shape is Circle
const isTriangle = isShape(Type.Triangle) // return type: shape is Triangle
CRice
  • 29,968
  • 4
  • 57
  • 70