GeezerVison is showing github.com — keyof ignores constraint · Issue #44985 · microsoft/TypeScript
keyof ignores constraint #44985DescriptionBug Report
🔎 Search Terms
keyof, generic constraint
🕗 Version & Regression Information
- This is a crash ❌ No
- This changed between versions _______ and _______
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about keyof
- I was unable to test this on prior versions because it seems to be consistent behavior in all tested versions
⏯ Playground Link
Playground link with relevant code
💻 Code
// Implementation of event emitter - sample without all methods
type Listener<Event> = (event: Event) => void;
// Here the "key: K" complains "An index signature parameter type must be either 'string' or 'number'"
type Listeners<Events, K extends string & keyof Events> = {[key: K]: Listener<Events[K]>[]}
class EventEmitter<Events extends {[key: string]: unknown}> {
#listeners: Listeners<Events, keyof Events>
constructor() {
this.#listeners = {}
}
}
// Usage
type MyEvents = {
connect: void
reconnect: number
error: Error
disconnect: void
}
const emitter = new EventEmitter<MyEvents>()
🙁 Actual behavior
I am getting ts errors even though the types work as expected. See // usage section
🙂 Expected behavior
The keyof returns the same type as the constraint generic and thus will be valid as index signature. ActivityMartinJohns commented on Jul 12, 2021 Having keyof T as an indexer type would require #26797. alesmenzel commented on Jul 12, 2021 So, does that mean it is currently not possible to do?
btw. why is K extends string & keyof Events not enough to make it an indexable type since it requires 'string' or 'number' and the generic K must be a string and also keyof Events? MartinJohns commented on Jul 12, 2021 The type string & keyof Events is the same as keyof Events. And a union of keys can not be an index signature. You can use a mapped type instead. Reacted by Andrii Dieiev and 流浪大法师 RyanCavanaugh commented on Jul 12, 2021 This works without issue
type Listeners<Events, K extends keyof Events> = Record<K, Listener<Events[K]>>; alesmenzel commented on Jul 12, 2021 Thanks guys,
the error message wasnt clear to me, but know I understand that you cant use a union of types which you get from the keyof - e.g. type union = 'a' | 'b' | 'c' to index but instead use it in either Record which handles the union case or use mapped type like {[key in K]: ... } instead of {[key: K]:
Lastly, do you know why there is the last error at the .push(listener) line in .on() method ts playground
Argument of type 'Listener<Events[Type]>' is not assignable to parameter of type 'Listener<Events[keyof Events]>'.
Type 'Events[keyof Events]' is not assignable to type 'Events[Type]'.
Type 'keyof Events' is not assignable to type 'Type'.
'keyof Events' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint 'string | number | symbol'.
Type 'string | number | symbol' is not assignable to type 'Type'.
'string | number | symbol' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint 'string | number | symbol'.
Type 'string' is not assignable to type 'Type'.
'string' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint 'string | number | symbol'.(2345)
it seems to me that those types should be the same, no? One is Events[keyof Events] (so basically any values of the Events) and the second one is the concrete event Events[Type] where the type is defined as constraint of keyof Events
on<Type extends keyof Events>(type: Type, listener: Listener<Events[Type]>): Unsubscribe {
if (!type) throw new Error('Cannot call <EventEmitter>.on without a type');
if (!listener) throw new Error('Cannot call <EventEmitter>.on without a listener');
this.#listeners[type] = this.#listeners[type] || [];
this.#listeners[type].push(listener);
return () => {
this.off(type, listener);
}
}alesmenzel commented on Jul 12, 2021 locked as resolved and limited conversation to collaborators on Oct 21, 2025
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|