Skip to content

Union of partial types: common super type of all union members is not assignable to union type #33243

Closed
@seansfkelley

Description

@seansfkelley

TypeScript Version: v3.5.1

Search Terms: union partial type assignable structure

Code

// Data types

interface Key {
  type: "key";
  key: string;
}

interface KeyValue {
  type: "key-value";
  key: string;
  value: string;
}

type Either = Key | KeyValue;

type Common = Pick<Either, "type" | "key">;

// Test cases

// Structurally-compatible assignment
const either: PartialEither = null as any as Common;

// Type-guarding
if (either.type === "key-value") {
  either.value;
}

// Problematic type definitions

// Using this intermediate type so I can still rely on 'type' as the discriminant property of
// the PartialEither type. The naive
// Pick<Either, "type" | "key"> & Partial<Omit<Either, "type" | "key">>
// would lose the relationship between `type` and shape, causing the type guard above to fail.
type _PartialEither<T extends Either> = Pick<T, "type" | "key"> & Partial<Omit<T, "type" | "key">>;

type PartialKey = _PartialEither<Key>;
type PartialKeyValue = _PartialEither<KeyValue>;

type PartialEither = PartialKey | PartialKeyValue;

// This type seems to force the compiler to reinterpret the partial types into a slightly
// different representation that's logically equivalent but properly assignable. From:
// https://stackoverflow.com/questions/57780109/union-of-partial-types-in-typescript-cant-be-type-narrowed
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// Change the definition to this version to see the test cases above work.
// type PartialEither = Expand<PartialKey | PartialKeyValue>;

Expected behavior: Line 21, the structurally-compatible assignment, compiles, without a workaround. Using the Expand type has no effect on assignability.

Actual behavior: Line 21 does not compile, stating

Type 'Pick<Either, "key" | "type">' is not assignable to type 'PartialEither'.
  Type 'Pick<Either, "key" | "type">' is not assignable to type '_PartialEither<KeyValue>'.
    Type 'Pick<Either, "key" | "type">' is not assignable to type 'Pick<KeyValue, "key" | "type">'.
      Types of property 'type' are incompatible.
        Type '"key" | "key-value"' is not assignable to type '"key-value"'.
          Type '"key"' is not assignable to type '"key-value"'.

Using the Expand helper type workaround fixes the assignability problem.

Playground Link: link

Related Issues: #18538, #19927

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions