Skip to content

Sub-class generics not working correctly with super-class constraint/default generics #30480

Closed
@milesj

Description

@milesj

TypeScript Version: typescript@^3.4.0-dev.20190316

Search Terms: generic constraints, generic inference, generic inheritance

Code

I'm updating my event emitter to type all possible listeners, so that all on and emit methods are strictly typed. This is done by using conditional and mapped types, to infer the argument and function signatures from an interface like so:

export interface TaskEvents {
  // Define args 
  fail: [Error];
  // Define args and return (entire func)
  pass: (value: any) => Promise<any>;
}

This works well in isolation, as defined here: https://github.com/milesj/boost/pull/45/files#diff-af915a397c4f33b89e6fba137b261584

However, when consumed in another package that is using generics to define the events, the type system breaks down. A few examples.

Constraints + default generics (DOESNT WORK)

This approach uses generic constraints and defaults to define the events. This is ideal so that sub-classing can inherit the parents events and everything works correctly.

export interface TaskEvents {
  fail: [Error];
  pass: [any];
  run: [any];
  skip: [any];
}

class Task<Events extends TaskEvents = TaskEvents> extends Emitter<Events> {}

task.emit('run', [something]);

However, this doesn't seem to work:
Screen Shot 2019-03-18 at 19 44 39
Screen Shot 2019-03-18 at 19 44 50

Defaults only (DOESNT WORK)

This approach only uses defaults and no constraints, but obviously doesn't work since the class isn't aware of the explicit events.

class Task<Events = TaskEvents> extends Emitter<Events> {}

task.emit('run', [something]);

Screen Shot 2019-03-18 at 19 46 36

Constraints only (DOESNT WORK)

Inverse of the previous example. This has the same problem as the first example.

class Task<Events extends TaskEvents> extends Emitter<Events> {}

task.emit('run', [something]);

Screen Shot 2019-03-18 at 19 44 39

Screen Shot 2019-03-18 at 19 44 50

No sub-class generics / Super-class explicitly defined (DOES WORK)

This approach does work.

class Task extends Emitter<TaskEvents> {}

task.emit('run', [something]);

Screen Shot 2019-03-18 at 19 49 52

However, this is not ideal since sub-classes can't override the defined events. An example as so:

export interface RoutineEvents extends TaskEvents {
  command: [string];
  'command.data': [string, string];
}

class Routine<Events extends RoutineEvents = RoutineEvents> extends Task<Events> {}

// Should inherit and work
routine.emit('run', [something]);

Expected behavior:

The constraints/defaults on class generics are inherited correctly.

Actual behavior:

They don't seem to be inherited correctly unless explicitly defined.

Playground Link:

PR has a handful of usage: milesj/boost#45

Related Issues:

Looked around a bit, found this?

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions