Open
Description
TypeScript Version: 3.x, 4.0.0-beta, nightly
Search Terms: dummy fake overload infer
Code
class FieldEvent<T extends Field> {
constructor(readonly field: T, readonly message: string) { }
}
class Field {
listen<T extends FieldEvent<Field>>(type: new (...args: any[]) => T, listener: (event: T) => void): void { }
}
class BeforeSetEvent<T extends number | string> extends FieldEvent<ValueField<T>> {
constructor(field: ValueField<T>, public newValue: T) { super(field, ""); }
}
class AfterSetEvent<T extends number | string> extends FieldEvent<ValueField<T>> {
constructor(field: ValueField<T>, readonly oldValue: T) { super(field, ""); }
}
class ValueField<T extends number | string> extends Field {
constructor(public value: T) { super(); }
// listen<U extends FieldEvent<Field>>(type: new (arg: this) => U, listener: (event: U) => void): void;
// listen<U extends FieldEvent<Field>>(type: new (...args: any[]) => U, listener: (event: U) => void): void;
listen<U extends FieldEvent<Field>>(type: new (...args: any[]) => U, listener: (event: U) => void): void {
super.listen(type, listener);
}
}
let field = new ValueField(0);
field.listen(FieldEvent, (event) => event.field); // event: FieldEvent<any>
field.listen(BeforeSetEvent, (event) => { event.field; event.newValue; }); // event: BeforeSetEvent<any>
field.listen(AfterSetEvent, (event) => { event.field; event.oldValue; }); // event: AfterSetEvent<any>
Expected behavior:
event
to be better inferred than any
.
Actual behavior:
event
inferred as any
.
But if we uncomment the 2 overloads, then event
is correctly inferred:
field.listen(FieldEvent, (event) => event.field); // event: FieldEvent<ValueField<0>>
field.listen(BeforeSetEvent, (event) => { event.field; event.newValue; }); // event: BeforeSetEvent<0>
field.listen(AfterSetEvent, (event) => { event.field; event.oldValue; }); // event: AfterSetEvent<0>
What's surprising (so the reason I consider it as a bug):
- The 1st overload doesn't match any signature.
- But it influences the way the other overload is inferred.
Note that if we replace (arg: this)
by ()
or another type, then:
field.listen(FieldEvent, (event) => event.field); // event: FieldEvent<Field>
field.listen(BeforeSetEvent, (event) => { event.field; event.newValue; }); // event: BeforeSetEvent<string | number>
field.listen(AfterSetEvent, (event) => { event.field; event.oldValue; }); // event: AfterSetEvent<string | number>
Playground Link: Playground Link
Related Issues: I first posted #36226 but was not able to solve my issue, until I found the (arg: this)
trick.