-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Description
π Search Terms
"extends narrowing", "existential types"
π Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about generics
β― Playground Link
π» Code
type Foo<K, T> = K extends keyof T ? T[K] : never;
function test1<K, T>(foo: Foo<K, T>) { }
function test2<K extends keyof T, T>(foo: T[K]) {
test1(foo);
// ^ Argument of type 'T[K]' is not assignable to parameter of type 'Foo<K, T>'
}
π Actual behavior
We get an error message:
Argument of type 'T[K]' is not assignable to parameter of type 'Foo<K, T>'
π Expected behavior
No error.
Additional information about the issue
I have tried asking this as a question on StackOverflow, but haven't received an answer that resolves whether this is an expected behavior or a current limitation of the compiler narrowing capabilities.
The problem came up during the rather complex typing for adding generics to the EventEmitter
class.
interface T {
myEvent: [string]
}
// It's now possible to have a specialized event emitter:
const emitter: EventEmitter<T> = new EventEmitter();
// We expect to be able to type the listener a straight-forward way:
type Listener<K extends keyof T> = (...args: T[K]) => void;
function on<K extends keyof T>(event: K, listener: Listener<K>): void {
// This won't work because `on` is typed using conditional typing:
emitter.on(event, listener);
}
The code example presented in this issue is a distillation of this issue where knowing that K extends keyof T
is not enough to narrow a conditional type with the exact same condition.
The following typing fixes the error:
// Yuck!
type Listener<K> = K extends keyof T ? (
(...args: T[K]) => void
)
: never;
Finally, if you're wondering why the typing of EventEmitter
uses conditional typing in this way, the answer is because it has to support the default case with all the bells and whistles (i.e. how it's been used in existing code).