2

I am trying to implement the "nanoevents" library in an extendable class.

You can look at the library's relevant type definitions here: https://github.com/ai/nanoevents/blob/128fac9/index.d.ts

// this is straight from the above link, only used as reference

interface EventsMap {
  [event: string]: any
}

interface DefaultEvents extends EventsMap {
  [event: string]: (...args: any) => void
}

// ...

export declare class Emitter<Events extends EventsMap = DefaultEvents> {
  // ...

   events: Partial<{ [E in keyof Events]: Events[E][] }>

  // ...

  on<K extends keyof Events> (this: this, event: K, cb: Events[K]): Unsubscribe

  // ...
}

I want to define my class as generic so that inheriting subclasses may define their relevant events and make use of the type checking.

import { createNanoEvents,Emitter,Unsubscribe } from "nanoevents";

class Extendable<Events extends {}={}>
{
  protected _emitter:Emitter<Events> = createNanoEvents<Events>();

  public on<E extends keyof Events>(event:E,callback:Events[E]):Unsubscribe
  {
    return this._emitter.on(event,callback);
  }
}

I have successfully extended my class here:

interface ExtendedEvents {
    'a':()=>void,
    'b':(c:number)=>void,
}

class Extended<Events extends ExtendedEvents=ExtendedEvents> extends Extendable<Events>
{
}

And now I want to pass it to the following function:

function foo(extendableType:typeof Extendable):void { /* ... */ }
foo(Extended); // <-- error here

Which fails with:

TS2345: Argument of type 'typeof Extended' is not assignable to type 'typeof Extendable'.
  Construct signature return types 'Extended<ExtendedEvents>' and 'Extendable<Events>' are incompatible.
    The types of '_emitter.events' are incompatible between these types.
      Type 'Partial<{ a: (() => void)[]; b: ((c: number) => void)[]; }>' is not assignable to type 'Partial<{ [E in keyof Events]: Events[E][]; }>'.
Drakuno
  • 67
  • 4
  • The type parameters take their default values in `foo(C: typeof Extendable)` and `ExtendedEvents` is a more specific constraint than `EventMap`. In other words, in `foo`, you could write `new C().on('if C is Extended this is a bug!', () => {})` – Aluan Haddad Sep 08 '20 at 19:34
  • I managed to work around the issue by marking all of the properties of `interface ExtendedEvents` as optional (equivalent to `Partial`). It works though I'm not really sure why. It seems the question boils down to "Why is `Partial<{ a: (() => void)[]; b: ((c: number) => void)[]; }>` not compatible with `Partial<{ [E in keyof Events]: Events[E][]; }>`" when `a` and `b` should be keys of `Events` and their types compatible too. – Drakuno Sep 08 '20 at 19:38
  • `{ a: (() => void)[]; b: ((c: number) => void)[]; }` _is_ compatible with `{ [E in keyof Events]: Events[E][]; }` (where `Events` is `{[k: string]:(...args:any[]) => void[]}`), but that doesn't mean that T extends `{ a: (() => void)[]; b: ((c: number) => void)[]; }` is are compatible with `{ [E in keyof Events]: Events[E][]; }`. – Aluan Haddad Sep 08 '20 at 19:41
  • This is something I do not understand. I understand how that particular extension you mention might break everything, but I don't see why that is relevant with marking the keys as optional. Besides, the type that is sent to `Partial<...>` is NOT the original `interface ExtendedEvents`. It is instead `{[EE in keyof EEvents]: EEvents[EE][]}` where `EEvents extends ExtendedEvents`, right? `EE` here should be constrained to `"a"|"b"`, and possibly augmented with more keys by another extension... And still, all of that should fit nicely into `Partial<{ [E in keyof Events]: Events[E][]; }>` I think. – Drakuno Sep 08 '20 at 20:28
  • 1
    It doesn't have anything to do with optional. `EEvents` does not extend `ExtendedEvents` it extends `EventMap`. Since you're not passing the generic through `foo` it could be anything allowed by tbe constraint in `Extendable` – Aluan Haddad Sep 08 '20 at 20:32
  • So, similar to this? https://stackoverflow.com/questions/56505560/could-be-instantiated-with-a-different-subtype-of-constraint-object – Drakuno Sep 08 '20 at 20:52
  • 1
    Similar yes. Here's one way you can correct it: `function foo(ExtendableConstructor: new (...args: any[]) => Extendable)`. – Aluan Haddad Sep 08 '20 at 21:02

0 Answers0