Microsoft/TypeScript

Intersection type with discriminated union type that includes all possible enum values cannot accept enum type

Open

#33,654 opened on Sep 28, 2019

View on GitHub
 (3 comments) (1 reaction) (0 assignees)TypeScript (48,455 stars) (6,726 forks)batch import
BugDomain: IntersectionHelp Wanted

Description

In TypeScript you can create a union type discriminated by an enum. If the union includes all possible enum values, you can create it with a value that is typed to the enum (not one of its values).

Given my example below, this is valid: const p: Payload = { type, value }

When you create a new type by intersecting the discriminated union type with another type definition, for example to add another field, this no longer works. It is now no longer possible to assign a value that is typed to the enum to the new type.

So this is invalid: const p: { id: string } & Payload = { id, type, value }

The workaround I can think of is the following, but it's pretty tedious:

type Identify<P> = P extends { type: infer T; value: infer V }
  ? { id: string; type: T; value: V }
  : never;

type IdentifiedPayload = Identify<Payload>;

TypeScript Version: 3.6.3

Search Terms: intersection discriminated union enum

Code

enum Type {
  Join = "join",
  Leave = "leave",
  Message = "message",
  Welcome = "welcome"
}

type Payload =
  | { type: Type.Join; value: { nickname: string } }
  | { type: Type.Leave; value: {} }
  | { type: Type.Message; value: { message: string } }
  | {
      type: Type.Welcome;
      value: { others: { id: string; nickname: string }[] };
    };

type IdentifiedPayload = { id: string } & Payload;

function parse(data: string): IdentifiedPayload {
  const [id, type, json] = data.split(" ", 3);
  if (typeof json !== "string") throw Error("received invalid data");
  if (!isType(type)) throw Error("invalid type " + type);
  const value = JSON.parse(json);
  // This line errors:
  return { id, type, value };
  // Note that TypeScript considers this valid:
  // const p: Payload = { type, value };
}

function isType(type: string): type is Type {
  return Object.values(Type).includes(type);
}

Expected behavior: Works

Actual behavior: Type '{ id: string; type: Type; value: any; }' is not assignable to type 'IdentifiedPayload'.

Playground Link: https://www.typescriptlang.org/play/index.html#code/KYOwrgtgBAKgngB2FA3gKClAUgewJYhQC8UARAFb4ikA0GUAMsAIYBuyJpANi+7fQFlgAZ2HMA5hzIQRYyf0wB1YFwDGOGcTIB3Fepmk0AXzRoALomQAFZnC45mAE2L0APqigWkALliWAdLgEANxQrMxcYMC+KFAgeKoA1iDMMr7CZgBOBOJQRnluHl7Rfkj+TGzAoeGRJSj5JpjuscW+8GVCohJVYRFRMVAyXZLpWTl5BU2o9JiYraXA-spqGlUzszX9HjhmABbAmcIDeI6j2SDiofFJKWlQGee5RgDaALp5wetGn+aWUACSjlAZjwADM8MBHDY7A5nCRYiczuN8gAyKDQ+xOH6gsAgVQgnCEBDMQ7AAAUjmYZmYSIuAEpfIDgWCIVDbJjnOhMOoQBkoM8TjRPJYheRhIT3iRKdT-MIEFw8GYyaQyEKAMx0z6YMFQMnFHCgqBiwlQACERE4DxypDpnl2mRw2igAFFMg7MsrMsBVMA8OxnAQaicoNLmDatVAdWTTXhhO1ycU6ba9g6na73crAxFg8UyFAANTCpCa+g8vmbKRYADKAHkAHL+YmksnGkAlzBesxgTKEBGOIXFIUVj7GUw4vEEwix+N6yy08QMovIWMLaYd4BdntQGsAI3I3rM-grwjJ8bp-gIqkiQJPic+JiAA

Related Issues:

Contributor guide