-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sub-class generics not working correctly with super-class constraint/default generics #30480
Comments
I tried this:
And it works. I'm using TS 3.3.3333. |
I noticed someone else had an issue with changes in 3.4 regarding conditional types; not sure if the underlying reason is related: #30489 |
@rjamesnw I don't see any differences with your example? Does the |
Ok, I've confirmed this doesn't work:
This seems to be an issue with the conditional type. It works when I do this:
The moment I add any condition it fails. |
Yeah, definitely seems like a bug then. Or perhaps one of those cases not supported by design. |
I tried another approach where the export interface TaskEvents {
fail: [Error];
pass: [any];
run: [any];
skip: [any];
}
class Task {
protected emitter: Emitter<TaskEvents>;
constructor() {
this.emitter = new Emitter();
}
}
export interface RoutineEvents extends TaskEvents {
command: [string];
'command.data': [string, string];
}
class Routine extends Task {
protected emitter: Emitter<RoutineEvents>;
constructor() {
super();
this.emitter = new Emitter();
}
} |
And also this approach which sort of combines the 2. Also doesnt work. Has the same problem as the original post. export interface TaskEvents {
fail: [Error];
pass: [any];
run: [any];
skip: [any];
}
class Task<Events extends TaskEvents = TaskEvents> {
protected emitter: Emitter<Events>;
constructor() {
this.emitter = new Emitter();
}
} |
Tried a few more things, still no luck. Any word? Is this a lost cause? |
@milesj I was hoping someone would come up with a TL;DR isolated repro that works in the Playground and demonstrates the behavior considered wrong here. Or is this a question? It's not obvious |
It really seems like you want class Task extends Emitter<TaskEvents> {
run() {
this.emit('run', [true]);
}
} Without a concrete type for |
Do you have any suggestion on trying to implement a feature like this? |
I'd need to understand why you think |
The main use is that
Not a 100% blocker but would be really really nice to have. |
That pattern is problematic because it's unsound (because Pushing the derived type in separately fixes this: class Task<TAdditional = {}> extends Emitter<TaskEvents> {
protected emitter: Emitter<TaskEvents>;
constructor() {
super();
this.emitter = new Emitter();
}
run() {
this.emit('run', [true]);
}
}
export interface RoutineEvents extends TaskEvents {
command: [string];
'command.data': [string, string];
}
class Routine extends Task<RoutineEvents> {
protected emitter: Emitter<TaskEvents & RoutineEvents>;
constructor() {
super();
this.emitter = new Emitter();
}
command(s: string) {
this.emit("run", [s]);
}
} |
Thank you, will give this a shot. |
Went with another approach, since the API changes are either breaking, or just make it uglier/too complicated. It's basically Angular events: milesj/boost#45 Thanks for the help/info! |
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
andemit
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: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.
However, this doesn't seem to work:
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.
Constraints only (DOESNT WORK)
Inverse of the previous example. This has the same problem as the first example.
No sub-class generics / Super-class explicitly defined (DOES WORK)
This approach does work.
However, this is not ideal since sub-classes can't override the defined events. An example as so:
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?
unknown
, same issues.The text was updated successfully, but these errors were encountered: