Skip to content

Wishlist: support for correlated union types #30581

Closed

Description

TypeScript Version: 3.4.0-dev.20190323

Search Terms

correlated union record types

Code

type NumberRecord = { kind: "n", v: number, f: (v: number) => void };
type StringRecord = { kind: "s", v: string, f: (v: string) => void };
type BooleanRecord = { kind: "b", v: boolean, f: (v: boolean) => void };
type UnionRecord = NumberRecord | StringRecord | BooleanRecord;

function processRecord(record: UnionRecord) {
  record.f(record.v); // error!
 // error msg in TS3.2 and below: can't call union of functions
 // error msg in TS3.3 and up: record.v not assignable to never
}

Expected behavior:

The implementation of processRecord() code compiles without error

Actual behavior:

The call of record.f(record.v) complains either that record.f is not callable (TS3.2 and below) or that record.v is not of type never (TS3.3 and up).

Playground Link: 🔗

Discussion:

Consider the discriminated union UnionRecord above. How can we convince the compiler that the implementation of processRecord() is type safe?

I made a previous suggestion (#25051) to deal with this, but it was closed as a duplicate of #7294, since record.f was perceived as a union of functions, which were not callable. Now #29011 is in place to deal with unions of functions, and the issue persists. Actually it's arguably worse, since the error message is even more confusing. ("Why does the compiler want never here?")

For now the only workarounds are type assertions (which are not safe) or to walk the compiler manually through the different constituents of the union type via type guards (which is repetitive and brittle).

Here are some questions on Stack Overflow that I've seen asked which run into this issue:

I don't really expect a type-safe and convenient solution to appear, but when someone asks on StackOverflow or elsewhere about why they can't get this to work, I'd like to point them here (or somewhere) for an official answer.

Note that this problem also shows up as issues with correlations across multiple arguments, whether union-of-rest-tuples or generics:

type MultiArgVersion = UnionRecord extends infer U ? U extends UnionRecord ?
  [v: U['v'], f: U['f']] : never : never;
// type MultiArgVersion = [v: number, f: (v: number) => void] | 
// [v: string, f: (v: string) => void] | 
// [v: boolean, f: (v: boolean) => void];

function processMultiArg([v, f]: MultiArgVersion) {
  f(v); // error!
  // errror msg
  // Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'.
}

function processMultiGeneric<T extends MultiArgVersion>(v: T[0], f: T[1]) {
  f(v); // error!
  // error msg
  // Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'.
}

Thanks!

Related Issues:
#25051: distributive control flow analaysis suggestion which would deal with this
#7294: unions of functions can't usually be called
#29011: unions of functions can now sometimes be called with intersections of parameters
#9998: control flow analysis is hard

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions