Description
openedon Mar 25, 2019
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:
- How to make “handler” lookup for “dispatcher” style function be typed correctly
- Typescript control flow analysis doesn't recognise a known type
- typescript yet another type lacks a call signature
- Type incompatibility error with no apparent incompatibility
- Select generic interface type from string parameter
- Why does typescript expect 'never' as function argument when retrieving the function type via generics?
- How to describe the relationship between elements of tuple in array of tuples via types in TypeScript?
- Argument of type {…} is not assignable to parameter of type 'never'
- TypeScript: How to map objects in a discriminated union to functions they can be called with?
- Type of properties in a generic function
- typescript inference without enumerating all possibilities
- Can the cast to any be avoided with better type definitions?
- Is there a way to create a type-guard for nested key access in Typescript?
- Map of heterogeneous functions in Typescript
- How to write typeguard for array of discriminated union types?
- Executing functions with arguments from a list of objects in TypeScript
- Error with Type 'string' is not assignable to type 'never'
- TypeScript generic type inference from function parameters
- Cannot assign correctly typed member to map
- Why is looking up a mapped object type causing an intersection type?
- Typescript mapped type unexpected value signature
- How do I Infer Parameter and Function Types when calling a function from dictionary lookup?
- Typescript: Generic Component Discriminating Between Different Input Props
- Typescript array with union types and generics requires fields to be optional to compile
- (GitHub issue, not SO question) Object requires unnecessary type checking #42473, "Object requires unnecessary type checking"
- In TypeScript, infer that a dynamic argument is the argument of the dynamic function
- Getting type guard like functionality from conditional typed function
- TypeScript: Type a function that can call any function that exists on an interface
- Type 'string | number' is not assignable to type 'ReturnType<Cat[M]>'
- TypeScript nested generic and conditional types assign error
- Implementing message handler in TypeScript using mapped types
- Typescript - using a dictionary of functions
- typescript abstract class method with union arguments
- TypeScript: Type of function that uses discriminated union gives error
- How can a TypeScript forEach, infer the types of a union, and change the return type
- How to discriminate intersection
- Map multiple compatible types from a Union type
- Union type signatures get “merged” (intersected) unexpectedly
- TypeScript incorrectly defines type
- Typescript and object literal lookups
- Typescript discriminated union narrowing not working
- Issue with using mapped types inside map
- How to make tsc to infer union type as a single type?
- TypeScript data mapper function argument infers never
- TypeScript, objects and type discrimination
- Generic type is collapsed to union when passed to another function
- Argument of type 'string | number' is not assignable to parameter of type 'never'
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